diff -Nru dotnet8-8.0.100-8.0.0~rc2/build.proj dotnet8-8.0.100-8.0.0/build.proj --- dotnet8-8.0.100-8.0.0~rc2/build.proj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/build.proj 2023-11-13 13:20:34.000000000 +0000 @@ -4,6 +4,7 @@ + @@ -57,6 +58,83 @@ + + + + + + + + + + $([System.IO.Path]::GetFileName('%(SymbolsTarball.Identity)')) + $(Filename.Split('.')[1]) + $(ArtifactsTmpDir)Symbols + $(UnifiedSymbolsLayout)/$(RepositoryName) + + + + + + + + + + + + $(OutputPath)dotnet-symbols-all-$(MicrosoftSourceBuildIntermediateInstallerVersion)-$(TargetRid).tar.gz + + + + + + + + + + + + + + + $(ArtifactsTmpDir)SdkSymbols + $(OutputPath)dotnet-symbols-sdk-$(MicrosoftSourceBuildIntermediateInstallerVersion)-$(TargetRid).tar.gz + $(ArtifactsTmpDir)Sdk + %(SdkTarballItem.Identity) + + + + + + + + + + + + + + + + @@ -45,50 +47,52 @@ + + - - - %(LinuxRid.Identity) - - - - - %(LinuxRid.Identity) - + + + %(UnixRid.Identity) + + + + + %(UnixRid.Identity) + - - %(RuntimePackWithLinuxRid.Identity).%(RuntimePackWithLinuxRid.LinuxRid) + + %(RuntimePackWithUnixRid.Identity).%(RuntimePackWithUnixRid.UnixRid) - - %(PortablePackageWithLinuxRid.Identity) + + %(PortablePackageWithUnixRid.Identity) - - runtime.%(PortablePackageWithLinuxRid.LinuxRid).%(PortablePackageWithLinuxRid.Identity) + + runtime.%(PortablePackageWithUnixRid.UnixRid).%(PortablePackageWithUnixRid.Identity) - - runtime.%(PortablePackageWithLinuxRid.LinuxRid).runtime.native.%(PortablePackageWithLinuxRid.Identity) + + runtime.%(PortablePackageWithUnixRid.UnixRid).runtime.native.%(PortablePackageWithUnixRid.Identity) diff -Nru dotnet8-8.0.100-8.0.0~rc2/eng/bootstrap/OverrideBootstrapVersions.props dotnet8-8.0.100-8.0.0/eng/bootstrap/OverrideBootstrapVersions.props --- dotnet8-8.0.100-8.0.0~rc2/eng/bootstrap/OverrideBootstrapVersions.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/eng/bootstrap/OverrideBootstrapVersions.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,11 +1,23 @@ - - 7.0.4-servicing.23107.6 + + 7.0.4-servicing.23107.6 + + $(NonshippingRuntimeVersionFor700) - $(NonshippingRuntimeVersionFor700) - $(NonshippingRuntimeVersionFor700) - $(NonshippingRuntimeVersionFor700) + 8.0.0-rc.2.23479.6 + $(Msft80RC2RuntimeVersion) + $(Msft80RC2RuntimeVersion) + $(Msft80RC2RuntimeVersion) + $(Msft80RC2RuntimeVersion) + $(Msft80RC2RuntimeVersion) + $(Msft80RC2RuntimeVersion) + $(Msft80RC2RuntimeVersion) + $(Msft80RC2RuntimeVersion) + $(Msft80RC2RuntimeVersion) + $(Msft80RC2RuntimeVersion) + $(Msft80RC2RuntimeVersion) + $(Msft80RC2RuntimeVersion) diff -Nru dotnet8-8.0.100-8.0.0~rc2/eng/common/sdk-task.ps1 dotnet8-8.0.100-8.0.0/eng/common/sdk-task.ps1 --- dotnet8-8.0.100-8.0.0~rc2/eng/common/sdk-task.ps1 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/eng/common/sdk-task.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -64,7 +64,7 @@ $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty } if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { - $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.6.0-2" -MemberType NoteProperty + $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.8.1-2" -MemberType NoteProperty } if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") { $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true diff -Nru dotnet8-8.0.100-8.0.0~rc2/eng/common/tools.ps1 dotnet8-8.0.100-8.0.0/eng/common/tools.ps1 --- dotnet8-8.0.100-8.0.0~rc2/eng/common/tools.ps1 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/eng/common/tools.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -379,13 +379,13 @@ } # Minimum VS version to require. - $vsMinVersionReqdStr = '17.6' + $vsMinVersionReqdStr = '17.7' $vsMinVersionReqd = [Version]::new($vsMinVersionReqdStr) # If the version of msbuild is going to be xcopied, # use this version. Version matches a package here: - # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/RoslynTools.MSBuild/versions/17.6.0-2 - $defaultXCopyMSBuildVersion = '17.6.0-2' + # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/RoslynTools.MSBuild/versions/17.8.1-2 + $defaultXCopyMSBuildVersion = '17.8.1-2' if (!$vsRequirements) { if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { diff -Nru dotnet8-8.0.100-8.0.0~rc2/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/CheckForPoison.cs dotnet8-8.0.100-8.0.0/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/CheckForPoison.cs --- dotnet8-8.0.100-8.0.0~rc2/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/CheckForPoison.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/CheckForPoison.cs 2023-11-13 13:20:34.000000000 +0000 @@ -12,8 +12,11 @@ using System.IO.Compression; using System.Linq; using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using System.Security.Cryptography; using System.Text; +using System.Text.RegularExpressions; using System.Xml; using System.Xml.Linq; @@ -30,6 +33,12 @@ public ITaskItem[] FilesToCheck { get; set; } /// + /// The path of the project directory to the FilesToCheck. + /// + [Required] + public string ProjectDirPath { get; set; } + + /// /// The output path for an XML poison report, if desired. /// public string PoisonReportOutputFilePath { get; set; } @@ -141,6 +150,10 @@ private const string PoisonMarker = "POISONED"; + private const string SbrpAttributeType = "System.Reflection.AssemblyMetadataAttribute"; + + private record CandidateFileEntry(string ExtractedPath, string DisplayPath); + public override bool Execute() { IEnumerable poisons = GetPoisonedFiles(FilesToCheck.Select(f => f.ItemSpec), HashCatalogFilePath, MarkerFileName); @@ -176,7 +189,9 @@ IEnumerable nonShippingPackages = GetAllNonShippingPackages(); IEnumerable catalogedPackages = ReadCatalog(catalogedPackagesFilePath); var poisons = new List(); - var candidateQueue = new Queue(initialCandidates); + var candidateQueue = new Queue(initialCandidates.Select(candidate => + new CandidateFileEntry(candidate, Utility.MakeRelativePath(candidate, ProjectDirPath)))); + if (!string.IsNullOrWhiteSpace(OverrideTempPath)) { Directory.CreateDirectory(OverrideTempPath); @@ -186,22 +201,22 @@ while (candidateQueue.Any()) { - var checking = candidateQueue.Dequeue(); + var candidate = candidateQueue.Dequeue(); // if this is a zip or NuPkg, extract it, check for the poison marker, and // add its contents to the list to be checked. - if (ZipFileExtensions.Concat(TarFileExtensions).Concat(TarGzFileExtensions).Any(e => checking.ToLowerInvariant().EndsWith(e))) + if (ZipFileExtensions.Concat(TarFileExtensions).Concat(TarGzFileExtensions).Any(e => candidate.ExtractedPath.ToLowerInvariant().EndsWith(e))) { - Log.LogMessage($"Zip or NuPkg file to check: {checking}"); + Log.LogMessage($"Zip or NuPkg file to check: {candidate.ExtractedPath}"); // Skip non-shipping packages - if (nonShippingPackages.Contains(Path.GetFileName(checking), StringComparer.OrdinalIgnoreCase)) + if (nonShippingPackages.Contains(Path.GetFileName(candidate.ExtractedPath), StringComparer.OrdinalIgnoreCase)) { continue; } - var tempCheckingDir = Path.Combine(tempDir.FullName, Path.GetFileNameWithoutExtension(checking)); - PoisonedFileEntry result = ExtractAndCheckZipFileOnly(catalogedPackages, checking, markerFileName, tempCheckingDir, candidateQueue); + var tempCheckingDir = Path.Combine(tempDir.FullName, Path.GetFileNameWithoutExtension(candidate.ExtractedPath)); + PoisonedFileEntry result = ExtractAndCheckZipFileOnly(catalogedPackages, candidate, markerFileName, tempCheckingDir, candidateQueue); if (result != null) { poisons.Add(result); @@ -209,7 +224,7 @@ } else { - PoisonedFileEntry result = CheckSingleFile(catalogedPackages, tempDir.FullName, checking); + PoisonedFileEntry result = CheckSingleFile(catalogedPackages, candidate); if (result != null) { poisons.Add(result); @@ -237,10 +252,12 @@ } } - private static PoisonedFileEntry CheckSingleFile(IEnumerable catalogedPackages, string rootPath, string fileToCheck) + private static PoisonedFileEntry CheckSingleFile(IEnumerable catalogedPackages, CandidateFileEntry candidate) { // skip some common files that get copied verbatim from nupkgs - LICENSE, _._, etc as well as // file types that we never care about - text files, .gitconfig, etc. + var fileToCheck = candidate.ExtractedPath; + if (FileNamesToSkip.Any(f => Path.GetFileName(fileToCheck).ToLowerInvariant() == f.ToLowerInvariant()) || FileExtensionsToSkip.Any(e => Path.GetExtension(fileToCheck).ToLowerInvariant() == e.ToLowerInvariant()) || (new FileInfo(fileToCheck).Length == 0)) @@ -249,7 +266,7 @@ } var poisonEntry = new PoisonedFileEntry(); - poisonEntry.Path = Utility.MakeRelativePath(fileToCheck, rootPath); + poisonEntry.Path = candidate.DisplayPath; // There seems to be some weird issues with using file streams both for hashing and assembly loading. // Copy everything into a memory stream to avoid these problems. @@ -286,7 +303,11 @@ try { AssemblyName asm = AssemblyName.GetAssemblyName(fileToCheck); - if (IsAssemblyPoisoned(fileToCheck)) + if (!candidate.DisplayPath.Contains("SourceBuildReferencePackages") && IsAssemblyFromSbrp(fileToCheck)) + { + poisonEntry.Type |= PoisonType.SourceBuildReferenceAssembly; + } + else if (IsAssemblyPoisoned(fileToCheck)) { poisonEntry.Type |= PoisonType.AssemblyAttribute; } @@ -320,9 +341,51 @@ return false; } - private static PoisonedFileEntry ExtractAndCheckZipFileOnly(IEnumerable catalogedPackages, string zipToCheck, string markerFileName, string tempDir, Queue futureFilesToCheck) + private static bool IsAssemblyFromSbrp(string assemblyPath) + { + using var stream = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var peReader = new PEReader(stream); + + MetadataReader reader = peReader.GetMetadataReader(); + return reader.CustomAttributes.Select(attrHandle => reader.GetCustomAttribute(attrHandle)) + .Any(attr => IsAttributeSbrp(reader, attr)); + } + + private static bool IsAttributeSbrp(MetadataReader reader, CustomAttribute attr) + { + string attributeType = string.Empty; + + if (attr.Constructor.Kind == HandleKind.MemberReference) + { + MemberReference mref = reader.GetMemberReference((MemberReferenceHandle)attr.Constructor); + + if (mref.Parent.Kind == HandleKind.TypeReference) + { + TypeReference tref = reader.GetTypeReference((TypeReferenceHandle)mref.Parent); + attributeType = $"{reader.GetString(tref.Namespace)}.{reader.GetString(tref.Name)}"; + } + } + + if (attributeType == SbrpAttributeType) + { + var decodedValue = attr.DecodeValue(DummyAttributeTypeProvider.Instance); + try + { + return decodedValue.FixedArguments[0].Value.ToString() == "source" && decodedValue.FixedArguments[1].Value.ToString() == "source-build-reference-packages"; + } + catch + { + throw new InvalidOperationException($"{SbrpAttributeType} is not formatted properly with a key, value pair."); + } + } + + return false; + } + + private static PoisonedFileEntry ExtractAndCheckZipFileOnly(IEnumerable catalogedPackages, CandidateFileEntry candidate, string markerFileName, string tempDir, Queue futureFilesToCheck) { var poisonEntry = new PoisonedFileEntry(); + var zipToCheck = candidate.ExtractedPath; poisonEntry.Path = zipToCheck; using (var sha = SHA256.Create()) @@ -375,8 +438,9 @@ foreach (var child in Directory.EnumerateFiles(tempDir, "*", SearchOption.AllDirectories)) { - // also add anything in this zip/package for checking - futureFilesToCheck.Enqueue(child); + string displayPath = $"{candidate.DisplayPath}/{child.Replace(tempDir, string.Empty).TrimStart(Path.DirectorySeparatorChar)}"; + + futureFilesToCheck.Enqueue(new CandidateFileEntry(child, displayPath)); } return poisonEntry.Type != PoisonType.None ? poisonEntry : null; diff -Nru dotnet8-8.0.100-8.0.0~rc2/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/DummyAttributeTypeProvider.cs dotnet8-8.0.100-8.0.0/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/DummyAttributeTypeProvider.cs --- dotnet8-8.0.100-8.0.0~rc2/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/DummyAttributeTypeProvider.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/DummyAttributeTypeProvider.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +namespace Microsoft.DotNet.SourceBuild.Tasks.LeakDetection +{ + + // An empty ICustomAttributeTypeProvider implementation is necessary to read metadata attribute values. + internal class DummyAttributeTypeProvider : ICustomAttributeTypeProvider + { + public static readonly DummyAttributeTypeProvider Instance = new(); + + public Type GetPrimitiveType(PrimitiveTypeCode typeCode) => default(Type); + + public Type GetSystemType() => default(Type); + + public Type GetSZArrayType(Type elementType) => default(Type); + + public Type GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) => default(Type); + + public Type GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) => default(Type); + + public Type GetTypeFromSerializedName(string name) => default(Type); + + public PrimitiveTypeCode GetUnderlyingEnumType(Type type) => default(PrimitiveTypeCode); + + public bool IsSystemType(Type type) => default(bool); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/MarkAndCatalogPackages.cs dotnet8-8.0.100-8.0.0/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/MarkAndCatalogPackages.cs --- dotnet8-8.0.100-8.0.0~rc2/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/MarkAndCatalogPackages.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/MarkAndCatalogPackages.cs 2023-11-13 13:20:34.000000000 +0000 @@ -155,6 +155,7 @@ } File.Delete(p.ItemSpec); File.Move(poisonedPackagePath, p.ItemSpec); + Directory.Delete(packageTempPath, true); } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/PoisonType.cs dotnet8-8.0.100-8.0.0/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/PoisonType.cs --- dotnet8-8.0.100-8.0.0~rc2/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/PoisonType.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.LeakDetection/PoisonType.cs 2023-11-13 13:20:34.000000000 +0000 @@ -11,5 +11,6 @@ Hash = 1, AssemblyAttribute = 2, NupkgFile = 4, + SourceBuildReferenceAssembly = 8, } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.XPlat/CreateSdkSymbolsLayout.cs dotnet8-8.0.100-8.0.0/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.XPlat/CreateSdkSymbolsLayout.cs --- dotnet8-8.0.100-8.0.0~rc2/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.XPlat/CreateSdkSymbolsLayout.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/eng/tools/tasks/Microsoft.DotNet.SourceBuild.Tasks.XPlat/CreateSdkSymbolsLayout.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,163 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.DotNet.Build.Tasks +{ + // Creates a symbols layout that matches the SDK layout + public class CreateSdkSymbolsLayout : Task + { + /// + /// Path to SDK layout. + /// + [Required] + public string SdkLayoutPath { get; set; } + + /// + /// Path to all source-built symbols, flat or with folder hierarchy. + /// + [Required] + public string AllSymbolsPath { get; set; } + + /// + /// Path to SDK symbols layout - will be created if it doesn't exist. + /// + [Required] + public string SdkSymbolsLayoutPath { get; set; } + + /// + /// If true, fails the build if any PDBs are missing. + /// + public bool FailOnMissingPDBs { get; set; } + + public override bool Execute() + { + IList filesWithoutPDBs = GenerateSymbolsLayout(IndexAllSymbols()); + if (filesWithoutPDBs.Count > 0) + { + LogErrorOrWarning(FailOnMissingPDBs, $"Did not find PDBs for the following SDK files:"); + foreach (string file in filesWithoutPDBs) + { + LogErrorOrWarning(FailOnMissingPDBs, file); + } + } + + return !Log.HasLoggedErrors; + } + + private void LogErrorOrWarning(bool isError, string message) + { + if (isError) + { + Log.LogError(message); + } + else + { + Log.LogWarning(message); + } + } + + private IList GenerateSymbolsLayout(Hashtable allPdbGuids) + { + List filesWithoutPDBs = new List(); + + if (Directory.Exists(SdkSymbolsLayoutPath)) + { + Directory.Delete(SdkSymbolsLayoutPath, true); + } + + foreach (string file in Directory.GetFiles(SdkLayoutPath, "*", SearchOption.AllDirectories)) + { + if (file.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) && + !file.EndsWith(".resources.dll", StringComparison.InvariantCultureIgnoreCase)) + { + string guid = string.Empty; + using var pdbStream = File.OpenRead(file); + using var peReader = new PEReader(pdbStream); + try + { + // Check if pdb is embedded + if (peReader.ReadDebugDirectory().Any(entry => entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb)) + { + continue; + } + + var debugDirectory = peReader.ReadDebugDirectory().First(entry => entry.Type == DebugDirectoryEntryType.CodeView); + var codeViewData = peReader.ReadCodeViewDebugDirectoryData(debugDirectory); + guid = $"{codeViewData.Guid.ToString("N").Replace("-", string.Empty)}"; + } + catch (Exception e) when (e is BadImageFormatException || e is InvalidOperationException) + { + // Ignore binaries without debug info + continue; + } + + if (guid != string.Empty) + { + string debugId = GetDebugId(guid, file); + if (!allPdbGuids.ContainsKey(debugId)) + { + filesWithoutPDBs.Add(file.Substring(SdkLayoutPath.Length + 1)); + } + else + { + // Copy matching pdb to symbols path, preserving sdk binary's hierarchy + string sourcePath = (string)allPdbGuids[debugId]!; + string destinationPath = + file.Replace(SdkLayoutPath, SdkSymbolsLayoutPath) + .Replace(Path.GetFileName(file), Path.GetFileName(sourcePath)); + + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!); + File.Copy(sourcePath, destinationPath, true); + } + } + } + } + + return filesWithoutPDBs; + } + + public Hashtable IndexAllSymbols() + { + Hashtable allPdbGuids = new Hashtable(); + + foreach (string file in Directory.GetFiles(AllSymbolsPath, "*.pdb", SearchOption.AllDirectories)) + { + using var pdbFileStream = File.OpenRead(file); + var metadataProvider = MetadataReaderProvider.FromPortablePdbStream(pdbFileStream); + var metadataReader = metadataProvider.GetMetadataReader(); + if (metadataReader.DebugMetadataHeader == null) + { + continue; + } + + var id = new BlobContentId(metadataReader.DebugMetadataHeader.Id); + string guid = $"{id.Guid:N}"; + string debugId = GetDebugId(guid, file); + if (!string.IsNullOrEmpty(guid) && !allPdbGuids.ContainsKey(debugId)) + { + allPdbGuids.Add(debugId, file); + } + } + + return allPdbGuids; + } + + /// + /// Calculates a debug Id from debug guid and filename. We use this as a key + /// in PDB hashtable. Guid is not enough due to collisions in several PDBs. + /// + private string GetDebugId(string guid, string file) => + $"{guid}.{Path.GetFileNameWithoutExtension(file)}".ToLower(); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/eng/Versions.props dotnet8-8.0.100-8.0.0/eng/Versions.props --- dotnet8-8.0.100-8.0.0~rc2/eng/Versions.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/eng/Versions.props 2023-11-13 13:20:34.000000000 +0000 @@ -30,7 +30,7 @@ These URLs can't be composed from their base URL and version as we read them from the prep.sh and pipeline scripts, outside of MSBuild. --> - https://dotnetcli.azureedge.net/source-built-artifacts/assets/Private.SourceBuilt.Artifacts.8.0.100-rc.1.23455.1.centos.8-x64.tar.gz - https://dotnetcli.azureedge.net/source-built-artifacts/sdks/dotnet-sdk-8.0.100-rc.1.23455.1-centos.8-x64.tar.gz + https://dotnetcli.azureedge.net/source-built-artifacts/assets/Private.SourceBuilt.Artifacts.8.0.100-rc.2.23502.1.centos.8-x64.tar.gz + https://dotnetcli.azureedge.net/source-built-artifacts/sdks/dotnet-sdk-8.0.100-rc.2.23502.1-centos.8-x64.tar.gz diff -Nru dotnet8-8.0.100-8.0.0~rc2/global.json dotnet8-8.0.100-8.0.0/global.json --- dotnet8-8.0.100-8.0.0~rc2/global.json 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/global.json 2023-11-13 13:20:34.000000000 +0000 @@ -1,6 +1,6 @@ { "tools": { - "dotnet": "8.0.100-rc.1.23455.8" + "dotnet": "8.0.100-rc.2.23502.2" }, "msbuild-sdks": { "Microsoft.Build.CentralPackageVersions": "2.0.1", diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/AllRepoVersions.props dotnet8-8.0.100-8.0.0/prereqs/git-info/AllRepoVersions.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/AllRepoVersions.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/AllRepoVersions.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,57 +1,59 @@  - 1d451c32dda2314c721adbf8829e1c0cd4e681ff - 8.0.0-beta.23463.1 - f0cc6b11bd8f0826c63d75483578e868c8abe75e - 8.0.0-rc.2.23480.2 - 89be445dd4936157533ad96bafb95f701430653a - 0.11.4-alpha.23468.2 + 39042b4048580366d35a7c1c4f4ce8fc0dbea4b4 + 8.0.0-beta.23516.4 + 4b00c57d7ccf9a4c7e2aef211ab6bd8af3ee2324 + 8.0.0-preview.1.23551.7 + 3f1acb59718cadf111a0a796681e3d3509bb3381 + 8.0.0-rtm.23531.12 + 45dd3a73dd5b64b010c4251303b3664bb30df029 + 0.11.4-alpha.23509.2 02fe27cd6a9b001c8feb7938e6ef4b3799745759 0.1.430701 5957c5c5f85f17c145e7fab4ece37ad6aafcded9 8.0.0-preview.6.23463.1 5ce78f66d89ea529e459abddb129ab36cb5bd936 7.0.0-preview.23211.1 - 8e857c92bff556d919f5f904bd9c777aade4afba - 8.0.0-rc.2.23473.3 - 0cf0f51b02d759e89f2acc09663493ab422548b0 - 8.0.447701 - 10f956e631a1efc0f7f5e49c626c494cd32b1f50 - 8.0.100-beta.23475.2 - 0abacfc2b649dfe89cb9bd91930afe95c10dec16 + 2406616d0e3a31d80b326e27c156955bfa41c791 + 8.0.0-rtm.23530.2 + 2651752953c0d41c8c7b8d661cf2237151af33d0 + 8.0.453106 + f41fe153f68dd6b20cf4f91de9ea1e55fc09bb20 + 8.0.100-beta.23523.6 + 57efcf1350ab087efe97ced8199ab0b494636adf 8.0.100 - 6cdef424154c976f04802b101e6be6292f8a8897 - 17.8.0-preview-23472-04 - 7fb5ed887352d2892797a365cfdd7bb8df029941 - 6.8.0-rc.117 - 2daeaaaed440a9c59b063b1578616850a0ccddd1 - 7.0.0-preview.23473.1 - 4a7701fd72094614897b33e4cb1d9640c221d862 - 3.11.0-beta1.23472.1 - bdd9c5ba66b00beebdc3516acc5e29b83efd89af - 4.8.0-3.23471.11 - 0b25e38ad32a69cd83ae246104b32449203cc71c - 8.0.0-rc.2.23475.17 - 67e671f384bee6937630b52b02cc78e69b27e280 - 8.0.100-rc.2.23480.5 - 6dbf3aaa0fc9664df86462f5c70b99800934fccd - 8.0.0-alpha.1.23471.2 - d825c6693d4e26f63aaa93c3c1d057faa098e347 - 8.0.0-alpha.1.23469.1 - ea3bd92278af83bae656ad9747c11f5a345e5b4a - 8.0.0-beta.23469.1 + 195e7f5a3a8e51c37d83cd9e54cb99dc3fc69c22 + 17.8.3-preview-23519-04 + 0dd5a1ea536201af94725353e4bc711d7560b246 + 6.8.0-rc.122 + 94fc3bd6fb6c8611fd4495e350db0560f46ece19 + 7.0.0-preview.23520.1 + b4d9a1334d5189172977ba8fddd00bda70161e4a + 3.11.0-beta1.23525.2 + f43cd10b737b6343956dee421cff8c50b602c788 + 4.8.0-3.23524.11 + 5535e31a712343a63f5d7d796cd874e563e5ac14 + 8.0.0-rtm.23531.3 + e9d13cbe7e8c1d52ce276a8655f52a87e1017c46 + 8.0.100-rtm.23551.6 + 3dc05150cf234f76f6936dcb2853d31a0da1f60e + 8.0.0-alpha.1.23518.1 + b4fa7f2e1e65ef49881be2ab2df27624280a8c55 + 8.0.0-alpha.1.23516.4 + e2f4720f9e7411122675568b984606c405b3bb53 + 8.0.0-beta.23510.2 2c8079e2e8e78c0cd11ac75a32014756136ecdb9 2.1.0-beta.23253.1 - 50dde258eee728e3ca730351dc6496639b55201a - 8.0.100-rc.2.23479.16 + 4085146587b833948a22587b36a108bcdb3f04a3 + 8.0.100-rtm.23531.6 1e5f3603af2277910aad946736ee23283e7f3e16 1.1.0-rc.23410.2 - cf7d549fc0197abaabec19d61d2c20d7a7b089f8 - 17.8.0-release-23468-02 + ae25c3b96fe433c60af70e3991ace49fcbf7e970 + 17.8.0-release-23523-03 9a1c3e1b7f0c8763d4c96e593961a61a72679a7b 7.0.0-preview.22423.2 - 194f32828726c3f1f63f79f3dc09b9e99c157b11 - 1.0.0-beta.23426.1 + 73f0850939d96131c28cf6ea6ee5aacb4da0083a + 1.0.0-beta.23475.1 \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/arcade.props dotnet8-8.0.100-8.0.0/prereqs/git-info/arcade.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/arcade.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/arcade.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,9 +1,9 @@  - 1d451c32dda2314c721adbf8829e1c0cd4e681ff - 20230913.1 - 8.0.0-beta.23463.1 + 39042b4048580366d35a7c1c4f4ce8fc0dbea4b4 + 20231016.4 + 8.0.0-beta.23516.4 beta false diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/aspire.props dotnet8-8.0.100-8.0.0/prereqs/git-info/aspire.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/aspire.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/aspire.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,10 @@ + + + + 4b00c57d7ccf9a4c7e2aef211ab6bd8af3ee2324 + 20231101.7 + 8.0.0-preview.1.23551.7 + preview.1 + false + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/aspnetcore.props dotnet8-8.0.100-8.0.0/prereqs/git-info/aspnetcore.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/aspnetcore.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/aspnetcore.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,10 +1,10 @@  - f0cc6b11bd8f0826c63d75483578e868c8abe75e - 20230930.2 - 8.0.0-rc.2.23480.2 - rc.2 + 3f1acb59718cadf111a0a796681e3d3509bb3381 + 20231031.12 + 8.0.0-rtm.23531.12 + rtm false \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/cecil.props dotnet8-8.0.100-8.0.0/prereqs/git-info/cecil.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/cecil.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/cecil.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,9 +1,9 @@  - 89be445dd4936157533ad96bafb95f701430653a - 20230918.2 - 0.11.4-alpha.23468.2 + 45dd3a73dd5b64b010c4251303b3664bb30df029 + 20231009.2 + 0.11.4-alpha.23509.2 alpha false diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/emsdk.props dotnet8-8.0.100-8.0.0/prereqs/git-info/emsdk.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/emsdk.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/emsdk.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,10 +1,10 @@  - 8e857c92bff556d919f5f904bd9c777aade4afba - 20230923.3 - 8.0.0-rc.2.23473.3 - rc.2 + 2406616d0e3a31d80b326e27c156955bfa41c791 + 20231030.2 + 8.0.0-rtm.23530.2 + rtm false \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/format.props dotnet8-8.0.100-8.0.0/prereqs/git-info/format.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/format.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/format.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,9 +1,9 @@  - 0cf0f51b02d759e89f2acc09663493ab422548b0 - 20230930.1 - 8.0.447701 + 2651752953c0d41c8c7b8d661cf2237151af33d0 + 20231101.1 + 8.0.453106 true diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/fsharp.props dotnet8-8.0.100-8.0.0/prereqs/git-info/fsharp.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/fsharp.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/fsharp.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,9 +1,9 @@  - 10f956e631a1efc0f7f5e49c626c494cd32b1f50 - 20230925.2 - 8.0.100-beta.23475.2 + f41fe153f68dd6b20cf4f91de9ea1e55fc09bb20 + 20231023.6 + 8.0.100-beta.23523.6 beta false diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/installer.props dotnet8-8.0.100-8.0.0/prereqs/git-info/installer.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/installer.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/installer.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,8 +1,8 @@  - 0abacfc2b649dfe89cb9bd91930afe95c10dec16 - 20231002.1 + 57efcf1350ab087efe97ced8199ab0b494636adf + 20231101.1 8.0.100 true diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/msbuild.props dotnet8-8.0.100-8.0.0/prereqs/git-info/msbuild.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/msbuild.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/msbuild.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,9 +1,9 @@  - 6cdef424154c976f04802b101e6be6292f8a8897 - 20230922.4 - 17.8.0-preview-23472-04 + 195e7f5a3a8e51c37d83cd9e54cb99dc3fc69c22 + 20231019.4 + 17.8.3-preview-23519-04 preview false diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/nuget-client.props dotnet8-8.0.100-8.0.0/prereqs/git-info/nuget-client.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/nuget-client.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/nuget-client.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,9 +1,9 @@  - 7fb5ed887352d2892797a365cfdd7bb8df029941 + 0dd5a1ea536201af94725353e4bc711d7560b246 20230927.1 - 6.8.0-rc.117 + 6.8.0-rc.122 rc false diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/razor.props dotnet8-8.0.100-8.0.0/prereqs/git-info/razor.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/razor.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/razor.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,9 +1,9 @@  - 2daeaaaed440a9c59b063b1578616850a0ccddd1 - 20230923.1 - 7.0.0-preview.23473.1 + 94fc3bd6fb6c8611fd4495e350db0560f46ece19 + 20231020.1 + 7.0.0-preview.23520.1 preview false diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/roslyn-analyzers.props dotnet8-8.0.100-8.0.0/prereqs/git-info/roslyn-analyzers.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/roslyn-analyzers.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/roslyn-analyzers.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,9 +1,9 @@  - 4a7701fd72094614897b33e4cb1d9640c221d862 - 20230922.1 - 3.11.0-beta1.23472.1 + b4d9a1334d5189172977ba8fddd00bda70161e4a + 20231025.2 + 3.11.0-beta1.23525.2 beta1 false diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/roslyn.props dotnet8-8.0.100-8.0.0/prereqs/git-info/roslyn.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/roslyn.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/roslyn.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,9 +1,9 @@  - bdd9c5ba66b00beebdc3516acc5e29b83efd89af - 20230921.11 - 4.8.0-3.23471.11 + f43cd10b737b6343956dee421cff8c50b602c788 + 20231024.11 + 4.8.0-3.23524.11 3 false diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/runtime.props dotnet8-8.0.100-8.0.0/prereqs/git-info/runtime.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/runtime.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/runtime.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,10 +1,10 @@  - 0b25e38ad32a69cd83ae246104b32449203cc71c - 20230925.17 - 8.0.0-rc.2.23475.17 - rc.2 + 5535e31a712343a63f5d7d796cd874e563e5ac14 + 20231031.3 + 8.0.0-rtm.23531.3 + rtm false \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/sdk.props dotnet8-8.0.100-8.0.0/prereqs/git-info/sdk.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/sdk.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/sdk.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,10 +1,10 @@  - 67e671f384bee6937630b52b02cc78e69b27e280 - 20230930.5 - 8.0.100-rc.2.23480.5 - rc.2 + e9d13cbe7e8c1d52ce276a8655f52a87e1017c46 + 20231101.6 + 8.0.100-rtm.23551.6 + rtm false \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/source-build-externals.props dotnet8-8.0.100-8.0.0/prereqs/git-info/source-build-externals.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/source-build-externals.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/source-build-externals.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,9 +1,9 @@  - 6dbf3aaa0fc9664df86462f5c70b99800934fccd - 20230921.2 - 8.0.0-alpha.1.23471.2 + 3dc05150cf234f76f6936dcb2853d31a0da1f60e + 20231018.1 + 8.0.0-alpha.1.23518.1 alpha.1 false diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/source-build-reference-packages.props dotnet8-8.0.100-8.0.0/prereqs/git-info/source-build-reference-packages.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/source-build-reference-packages.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/source-build-reference-packages.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,9 +1,9 @@  - d825c6693d4e26f63aaa93c3c1d057faa098e347 - 20230919.1 - 8.0.0-alpha.1.23469.1 + b4fa7f2e1e65ef49881be2ab2df27624280a8c55 + 20231016.4 + 8.0.0-alpha.1.23516.4 alpha.1 false diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/sourcelink.props dotnet8-8.0.100-8.0.0/prereqs/git-info/sourcelink.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/sourcelink.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/sourcelink.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,9 +1,9 @@  - ea3bd92278af83bae656ad9747c11f5a345e5b4a - 20230919.1 - 8.0.0-beta.23469.1 + e2f4720f9e7411122675568b984606c405b3bb53 + 20231010.2 + 8.0.0-beta.23510.2 beta false diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/templating.props dotnet8-8.0.100-8.0.0/prereqs/git-info/templating.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/templating.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/templating.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,10 +1,10 @@  - 50dde258eee728e3ca730351dc6496639b55201a - 20230929.16 - 8.0.100-rc.2.23479.16 - rc.2 + 4085146587b833948a22587b36a108bcdb3f04a3 + 20231031.6 + 8.0.100-rtm.23531.6 + rtm false \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/vstest.props dotnet8-8.0.100-8.0.0/prereqs/git-info/vstest.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/vstest.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/vstest.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,9 +1,9 @@  - cf7d549fc0197abaabec19d61d2c20d7a7b089f8 - 20230918.2 - 17.8.0-release-23468-02 + ae25c3b96fe433c60af70e3991ace49fcbf7e970 + 20231023.3 + 17.8.0-release-23523-03 release false diff -Nru dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/xliff-tasks.props dotnet8-8.0.100-8.0.0/prereqs/git-info/xliff-tasks.props --- dotnet8-8.0.100-8.0.0~rc2/prereqs/git-info/xliff-tasks.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/prereqs/git-info/xliff-tasks.props 2023-11-13 13:20:34.000000000 +0000 @@ -1,9 +1,9 @@  - 194f32828726c3f1f63f79f3dc09b9e99c157b11 - 20230826.1 - 1.0.0-beta.23426.1 + 73f0850939d96131c28cf6ea6ee5aacb4da0083a + 20230925.1 + 1.0.0-beta.23475.1 beta false diff -Nru dotnet8-8.0.100-8.0.0~rc2/README.md dotnet8-8.0.100-8.0.0/README.md --- dotnet8-8.0.100-8.0.0~rc2/README.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/README.md 2023-11-13 13:20:34.000000000 +0000 @@ -144,6 +144,12 @@ In practice, this means that when calling the main build script, you need to provide additional arguments when building outside of a context of a git repository. Alternatively, you can also provide a manifest file where this information can be read from. This file (`release.json`) can be found attached with the [dotnet/dotnet release](https://github.com/dotnet/dotnet/releases). +### Synchronizing code into the VMR + +Sometimes you want to make a change in a repository and test that change in the VMR. You could of course make the change in the VMR directly (locally, as the VMR is read-only for now) but in case it's already available in your repository, you can synchronize it into the VMR (again locally). + +To do this, you can start a [dotnet/dotnet](https://github.com/dotnet/dotnet) Codespace. You will see instructions right when the Codespace starts. Alternatively, you can clone the repository locally and use the `[eng/vmr-sync.sh](../../eng/vmr-sync.sh)` script to do that. Please refer to the documentation in the script for more details. + ## List of components To enable full offline source-building of the VMR, we have no other choice than to synchronize all the necessary code into the VMR. This also includes any code referenced via git submodules. More details on why and how this is done can be found here: @@ -154,15 +160,17 @@ - `src/arcade` -*[dotnet/arcade@1d451c3](https://github.com/dotnet/arcade/commit/1d451c32dda2314c721adbf8829e1c0cd4e681ff)* +*[dotnet/arcade@39042b4](https://github.com/dotnet/arcade/commit/39042b4048580366d35a7c1c4f4ce8fc0dbea4b4)* +- `src/aspire` +*[_git/dotnet-aspire@4b00c57](https://dev.azure.com/dnceng/internal/_git/dotnet-aspire/commit/4b00c57d7ccf9a4c7e2aef211ab6bd8af3ee2324)* - `src/aspnetcore` -*[_git/dotnet-aspnetcore@f0cc6b1](https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore/commit/f0cc6b11bd8f0826c63d75483578e868c8abe75e)* +*[_git/dotnet-aspnetcore@3f1acb5](https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore/commit/3f1acb59718cadf111a0a796681e3d3509bb3381)* - `src/aspnetcore/src/submodules/googletest` *[google/googletest@7e33b6a](https://github.com/google/googletest/commit/7e33b6a1c497ced1e98fc60175aeb4678419281c)* - `src/aspnetcore/src/submodules/MessagePack-CSharp` *[aspnet/MessagePack-CSharp@ecc4e18](https://github.com/aspnet/MessagePack-CSharp/commit/ecc4e18ad7a0c7db51cd7e3d2997a291ed01444d)* - `src/cecil` -*[dotnet/cecil@89be445](https://github.com/dotnet/cecil/commit/89be445dd4936157533ad96bafb95f701430653a)* +*[dotnet/cecil@45dd3a7](https://github.com/dotnet/cecil/commit/45dd3a73dd5b64b010c4251303b3664bb30df029)* - `src/command-line-api` *[dotnet/command-line-api@02fe27c](https://github.com/dotnet/command-line-api/commit/02fe27cd6a9b001c8feb7938e6ef4b3799745759)* - `src/deployment-tools` @@ -170,37 +178,37 @@ - `src/diagnostics` *[dotnet/diagnostics@5ce78f6](https://github.com/dotnet/diagnostics/commit/5ce78f66d89ea529e459abddb129ab36cb5bd936)* - `src/emsdk` -*[dotnet/emsdk@8e857c9](https://github.com/dotnet/emsdk/commit/8e857c92bff556d919f5f904bd9c777aade4afba)* +*[dotnet/emsdk@2406616](https://github.com/dotnet/emsdk/commit/2406616d0e3a31d80b326e27c156955bfa41c791)* - `src/format` -*[dotnet/format@0cf0f51](https://github.com/dotnet/format/commit/0cf0f51b02d759e89f2acc09663493ab422548b0)* +*[_git/dotnet-format@2651752](https://dev.azure.com/dnceng/internal/_git/dotnet-format/commit/2651752953c0d41c8c7b8d661cf2237151af33d0)* - `src/fsharp` -*[dotnet/fsharp@10f956e](https://github.com/dotnet/fsharp/commit/10f956e631a1efc0f7f5e49c626c494cd32b1f50)* +*[dotnet/fsharp@f41fe15](https://github.com/dotnet/fsharp/commit/f41fe153f68dd6b20cf4f91de9ea1e55fc09bb20)* - `src/installer` -*[dotnet/installer@0abacfc](https://github.com/dotnet/installer/commit/0abacfc2b649dfe89cb9bd91930afe95c10dec16)* +*[dotnet/installer@57efcf1](https://github.com/dotnet/installer/commit/57efcf1350ab087efe97ced8199ab0b494636adf)* - `src/msbuild` -*[dotnet/msbuild@6cdef42](https://github.com/dotnet/msbuild/commit/6cdef424154c976f04802b101e6be6292f8a8897)* +*[dotnet/msbuild@195e7f5](https://github.com/dotnet/msbuild/commit/195e7f5a3a8e51c37d83cd9e54cb99dc3fc69c22)* - `src/nuget-client` -*[nuget/nuget.client@7fb5ed8](https://github.com/nuget/nuget.client/commit/7fb5ed887352d2892797a365cfdd7bb8df029941)* +*[nuget/nuget.client@0dd5a1e](https://github.com/nuget/nuget.client/commit/0dd5a1ea536201af94725353e4bc711d7560b246)* - `src/nuget-client/submodules/NuGet.Build.Localization` *[NuGet/NuGet.Build.Localization@f15db7b](https://github.com/NuGet/NuGet.Build.Localization/commit/f15db7b7c6f5affbea268632ef8333d2687c8031)* - `src/razor` -*[dotnet/razor@2daeaaa](https://github.com/dotnet/razor/commit/2daeaaaed440a9c59b063b1578616850a0ccddd1)* +*[dotnet/razor@94fc3bd](https://github.com/dotnet/razor/commit/94fc3bd6fb6c8611fd4495e350db0560f46ece19)* - `src/roslyn` -*[dotnet/roslyn@bdd9c5b](https://github.com/dotnet/roslyn/commit/bdd9c5ba66b00beebdc3516acc5e29b83efd89af)* +*[dotnet/roslyn@f43cd10](https://github.com/dotnet/roslyn/commit/f43cd10b737b6343956dee421cff8c50b602c788)* - `src/roslyn-analyzers` -*[dotnet/roslyn-analyzers@4a7701f](https://github.com/dotnet/roslyn-analyzers/commit/4a7701fd72094614897b33e4cb1d9640c221d862)* +*[dotnet/roslyn-analyzers@b4d9a13](https://github.com/dotnet/roslyn-analyzers/commit/b4d9a1334d5189172977ba8fddd00bda70161e4a)* - `src/runtime` -*[_git/dotnet-runtime@0b25e38](https://dev.azure.com/dnceng/internal/_git/dotnet-runtime/commit/0b25e38ad32a69cd83ae246104b32449203cc71c)* +*[_git/dotnet-runtime@5535e31](https://dev.azure.com/dnceng/internal/_git/dotnet-runtime/commit/5535e31a712343a63f5d7d796cd874e563e5ac14)* - `src/sdk` -*[_git/dotnet-sdk@67e671f](https://dev.azure.com/dnceng/internal/_git/dotnet-sdk/commit/67e671f384bee6937630b52b02cc78e69b27e280)* +*[_git/dotnet-sdk@e9d13cb](https://dev.azure.com/dnceng/internal/_git/dotnet-sdk/commit/e9d13cbe7e8c1d52ce276a8655f52a87e1017c46)* - `src/source-build-externals` -*[dotnet/source-build-externals@6dbf3aa](https://github.com/dotnet/source-build-externals/commit/6dbf3aaa0fc9664df86462f5c70b99800934fccd)* +*[dotnet/source-build-externals@3dc0515](https://github.com/dotnet/source-build-externals/commit/3dc05150cf234f76f6936dcb2853d31a0da1f60e)* - `src/source-build-externals/src/abstractions-xunit` *[xunit/abstractions.xunit@b75d54d](https://github.com/xunit/abstractions.xunit/commit/b75d54d73b141709f805c2001b16f3dd4d71539d)* - `src/source-build-externals/src/application-insights` *[Microsoft/ApplicationInsights-dotnet@5e2e7dd](https://github.com/Microsoft/ApplicationInsights-dotnet/commit/5e2e7ddda961ec0e16a75b1ae0a37f6a13c777f5)* - `src/source-build-externals/src/azure-activedirectory-identitymodel-extensions-for-dotnet` - *[AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet@bf4cb25](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/commit/bf4cb251a85f1b27bbb208c703f6f3105bdb24ca)* + *[AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet@bb354ce](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/commit/bb354ceabed19189245e075abb864f327b6c14ad)* - `src/source-build-externals/src/cssparser` *[dotnet/cssparser@0d59611](https://github.com/dotnet/cssparser/commit/0d59611784841735a7778a67aa6e9d8d000c861f)* - `src/source-build-externals/src/docker-creds-provider` @@ -220,21 +228,21 @@ - `src/source-build-externals/src/xunit/tools/media` *[xunit/media@5738b6e](https://github.com/xunit/media/commit/5738b6e86f08e0389c4392b939c20e3eca2d9822)* - `src/source-build-reference-packages` -*[dotnet/source-build-reference-packages@d825c66](https://github.com/dotnet/source-build-reference-packages/commit/d825c6693d4e26f63aaa93c3c1d057faa098e347)* +*[dotnet/source-build-reference-packages@b4fa7f2](https://github.com/dotnet/source-build-reference-packages/commit/b4fa7f2e1e65ef49881be2ab2df27624280a8c55)* - `src/sourcelink` -*[dotnet/sourcelink@ea3bd92](https://github.com/dotnet/sourcelink/commit/ea3bd92278af83bae656ad9747c11f5a345e5b4a)* +*[dotnet/sourcelink@e2f4720](https://github.com/dotnet/sourcelink/commit/e2f4720f9e7411122675568b984606c405b3bb53)* - `src/symreader` *[dotnet/symreader@2c8079e](https://github.com/dotnet/symreader/commit/2c8079e2e8e78c0cd11ac75a32014756136ecdb9)* - `src/templating` -*[_git/dotnet-templating@50dde25](https://dev.azure.com/dnceng/internal/_git/dotnet-templating/commit/50dde258eee728e3ca730351dc6496639b55201a)* +*[_git/dotnet-templating@4085146](https://dev.azure.com/dnceng/internal/_git/dotnet-templating/commit/4085146587b833948a22587b36a108bcdb3f04a3)* - `src/test-templates` *[dotnet/test-templates@1e5f360](https://github.com/dotnet/test-templates/commit/1e5f3603af2277910aad946736ee23283e7f3e16)* - `src/vstest` -*[microsoft/vstest@cf7d549](https://github.com/microsoft/vstest/commit/cf7d549fc0197abaabec19d61d2c20d7a7b089f8)* +*[microsoft/vstest@ae25c3b](https://github.com/microsoft/vstest/commit/ae25c3b96fe433c60af70e3991ace49fcbf7e970)* - `src/xdt` *[dotnet/xdt@9a1c3e1](https://github.com/dotnet/xdt/commit/9a1c3e1b7f0c8763d4c96e593961a61a72679a7b)* - `src/xliff-tasks` -*[dotnet/xliff-tasks@194f328](https://github.com/dotnet/xliff-tasks/commit/194f32828726c3f1f63f79f3dc09b9e99c157b11)* +*[dotnet/xliff-tasks@73f0850](https://github.com/dotnet/xliff-tasks/commit/73f0850939d96131c28cf6ea6ee5aacb4da0083a)* The repository also contains a [JSON manifest](https://github.com/dotnet/dotnet/blob/main/src/source-manifest.json) listing all components in a machine-readable format. diff -Nru dotnet8-8.0.100-8.0.0~rc2/release.info dotnet8-8.0.100-8.0.0/release.info --- dotnet8-8.0.100-8.0.0~rc2/release.info 2023-10-18 18:08:36.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/release.info 2023-11-13 13:20:41.000000000 +0000 @@ -1,2 +1,2 @@ RM_GIT_REPO=https://github.com/dotnet/dotnet -RM_GIT_COMMIT=1e872358329855089d8d14cec1f06d5b075824b5 +RM_GIT_COMMIT=40e7f014ff784457efffa58074549735e30772ae diff -Nru dotnet8-8.0.100-8.0.0~rc2/repo-projects/aspire.proj dotnet8-8.0.100-8.0.0/repo-projects/aspire.proj --- dotnet8-8.0.100-8.0.0~rc2/repo-projects/aspire.proj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/repo-projects/aspire.proj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,13 @@ + + + + + $(StandardSourceBuildCommand) $(StandardSourceBuildArgs) + + + + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/repo-projects/dotnet.proj dotnet8-8.0.100-8.0.0/repo-projects/dotnet.proj --- dotnet8-8.0.100-8.0.0~rc2/repo-projects/dotnet.proj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/repo-projects/dotnet.proj 2023-11-13 13:20:34.000000000 +0000 @@ -45,6 +45,7 @@ + diff -Nru dotnet8-8.0.100-8.0.0~rc2/repo-projects/installer.proj dotnet8-8.0.100-8.0.0/repo-projects/installer.proj --- dotnet8-8.0.100-8.0.0~rc2/repo-projects/installer.proj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/repo-projects/installer.proj 2023-11-13 13:20:34.000000000 +0000 @@ -45,12 +45,6 @@ - - - - diff -Nru dotnet8-8.0.100-8.0.0~rc2/repo-projects/sdk.proj dotnet8-8.0.100-8.0.0/repo-projects/sdk.proj --- dotnet8-8.0.100-8.0.0~rc2/repo-projects/sdk.proj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/repo-projects/sdk.proj 2023-11-13 13:20:34.000000000 +0000 @@ -23,15 +23,6 @@ - - - - - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/azure-pipelines.yml dotnet8-8.0.100-8.0.0/src/arcade/azure-pipelines.yml --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/azure-pipelines.yml 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/azure-pipelines.yml 2023-11-13 13:20:34.000000000 +0000 @@ -7,6 +7,7 @@ - release/5.0 - release/6.0 - release/7.0 + - release/8.0 paths: include: - '*' @@ -30,6 +31,7 @@ - release/5.0 - release/6.0 - release/7.0 + - release/8.0 - templates paths: include: diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/AzureDevOps/AzureDevOpsOnboarding.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/AzureDevOps/AzureDevOpsOnboarding.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/AzureDevOps/AzureDevOpsOnboarding.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/AzureDevOps/AzureDevOpsOnboarding.md 2023-11-13 13:20:34.000000000 +0000 @@ -54,7 +54,7 @@ ### Git (internal) connections -See the [dotnet-bot-github-service-endpoint documentation](https://github.com/dotnet/arcade/blob/main/Documentation/Project-Docs/VSTS/dotnet-bot-github-service-endpoint.md#dotnet-bot-github-service-endpoint) +See the [dotnet-bot-github-service-endpoint documentation](https://github.com/dotnet/dnceng/blob/main/Documentation/ProjectDocs/VSTS/dotnet-bot-github-service-endpoint.md#dotnet-bot-github-service-endpoint) ## Agent queues diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/DevWorkflow/Queue-Insights-Documentation.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/DevWorkflow/Queue-Insights-Documentation.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/DevWorkflow/Queue-Insights-Documentation.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/DevWorkflow/Queue-Insights-Documentation.md 2023-11-13 13:20:34.000000000 +0000 @@ -26,7 +26,7 @@ You should interpret this data as this pipeline should finish around the *expected* time but may finish as quick as the *lower* time or take as long as the *higher* time. -This data is calculated from our build telemetry, and you may visit our [one-pager](../TeamProcess/One-Pagers/pipeline-machine-learning-arcade8824.md) for how it works. +This data is calculated from our build telemetry, and you may visit our [one-pager](https://github.com/dotnet/dnceng/blob/main/Documentation/TeamProcess/One-Pagers/pipeline-machine-learning-arcade8824.md) for how it works. ### Multi-modal distributions diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Operations/dnceng-operational-responsibilities.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Operations/dnceng-operational-responsibilities.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Operations/dnceng-operational-responsibilities.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Operations/dnceng-operational-responsibilities.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,177 +0,0 @@ -# .NET Core Engineering DevOps V-Team Responsibilities (Tentative short name : "DncDevOps") - -Note that TKapin and IlyaS have already spent some time thinking about this problem. That document is [here](https://microsofteur-my.sharepoint.com/:w:/g/personal/tokapin_microsoft_com/EWK52KdVvIZCsfLqe6idj6QBYzML12mx82xsmwtGt6H-Ug?e=PiAiUU) - -The above doc defines developer operations work as "Long lived, predictable, repetitive, and possibly highly manual work that needs to be performed periodically to keep providing healthy and secure services to our customers. For the physical world analogy, one could think of operational work as “turning the crank” of a complex machine to keep it operating." - -## "North Star" statement - -The .NET Core Engineering DevOps (DncDevOps) team strives to use its resources to make daily, repetitive operations undertaken by the team knowable, repeatable, automated (where possible), investigate-able, visible and secure. In doing so it will provide simple means for team members to understand existing and onboard new processes to this ownership. Processes and documentation should strive to be sufficient for vendors or new team members to be able to succeed at executing them. - -### Key principles - -- 'Manual' isn't a dirty word. Document and get running first, automate later. -- Strive for simple, procedural guidance for processes. -- Anything developed by this team needs to be written down, validated by someone else than the author, and findable by all -- Don't lose issues that don't meet the below bar or FR's; keep customer needs moving until closed or pathed somewhere. -- When we ask for a document, we have a template for it and the template _says where it should be put_. - -## Bar for activities owned by DncDevOps - -While exceptions will be made on a case-by-case basis, the following rubric should be applied to incoming requests determining whether it's appropriate to be handled by the DncDevOps team. Note that many of the tasks that will be handled eventually by DncDevOps are handled by the first responder team today. - -*Is the request for work in an established area?* DncDevOps should not be owning tasks or processes under their first round (or two, depending on complexity) of development; owned areas should be as close as possible to feature- and documentation-complete, and ready for daily usage. By definition, functionality already used in a .NET Core release shall be considered to be established. If this is not the case, work should be handled by the feature's active v-team, or possibly the First Responders (FR) team. - -*Is the work in an area under active development?* The DncDevOps team will work with epic teams to ensure that sustaining operations tasks are documented and reviewed before epic signoff, but in general while these areas are being developed it is preferable for that team to handle day-to-day tasks. - -*Is the work addressing immediate customer pain?* Regardless of how manual they may be, short-term customer pain issues should be handled by the First Responders team. Examples include creating a custom branch in a repository, adding a new service connection to an Azure DevOps project, or a one-off investigation of a particular error being faced by the customer. DncDevOps tasks include internal-only issues that have no short-term customer impact. They should represent tasks that repeat on at least weekly or biweekly cadence, such as (using the above "no" list as examples) monitoring the active branches used to ship a repo, keeping track of the service connections we create (why they exist, when they expire, who uses them), fixing issues found in FR investigations, or monitoring ongoing builds by a group of repositories to keep them healthy. - -*Is the task pertinent to the daily functioning of the .NET Core Engineering team or a team it supports?* One should be to make a clear, concise value statement about a DncDevOps-owned area's usefulness related to daily work by dnceng or a team it supports, whether it be for reliability, security, or general hygiene purposes. - -If you can reasonably answer "yes" to this list of questions, you're likely looking at something that should be housed in the "operations" epic. There will be quite a bit of overlap between DncDevOps and First-Responders, especially in the beginning, as these teams are meant to function as a complementary pair. Things which are not feature work but aren't devops either include FR work that addresses immediate customer pain, build failure investigations, guiding users on Teams, and really most one-off work items - -## What is the process by which we onboard DncDevOps tasks? - -If you've read the above bar and have something you'd like to onboard as a operational responsibility (or make changes to the existing processes), please follow the below process: - -- Create an issue in the https://github.com/dotnet/core-eng repository with the 'Proposed-for-DncDevOps' label. - -Suggested issue template: - -``` - -- [ ] This issue is relevant to daily .NET Core Engineering tasks -- [ ] This issue describes ownership of an ongoing responsibility pertinent to the .NET Core Engineering Services team - -Related issue(s) / documentation: - - - -- Requirements / estimated daily cost of ownership: - - -- (Known) automation debt to pay: - - -- Current state: - - -- Benefit of DncDevOps owning this: - - -Proposed DncDevOps ownership: -``` - -- At least once a week, tech leads of the FR and operations teams will review all issues with this tag. If accepted, the issue will be either converted to an epic or directly added as a child of the "DncDevOps work" epic in its backlog, sorted by priority order at the discretion of these teams. - -- If rejected for DncDevOps, we can have the conversation about sending the issue to FR or general triage process. This does not mean the issues just go to a dumping epic; issues with real consistent pain, customer or internal, should not just 'rot'. Unfortunately sometimes this will necessitate the user who wants this addressed to continue advocating for it. -- The goal is that automation will be added/found for as much of the process as reasonably possible, always striving for reuse / addition to existing systems. - -- For accepted tasks: - - - Work will be placed into the ZenHub backlog for the [Operations epic](https://github.com/dotnet/core-eng/issues/14471) - - Using the terminology of the above linked document, at a high level the next steps are: - - Phase 1: Gathering requirements (ideally this stage is already done, performed by whoever is authoring the issue, but the operations team will need to evaluate this and agree with it). This may be part of the acceptance process, since the requirements directly influence cost. - - Phase 2: Designing the routine. By the end of this stage, documentation of process should be under https://github.com/dotnet/core-eng/tree/main/Documentation/process/{area-name}. - - - Documentation in this folder should include at least a "operations-info.md" based off [process-info-template.md](./process-info-template.md) containing: - - Frequency of operations performed, with start/end dates as applicable - - Details and links to any source code / - - Step-by-step instructions which should be signed off by one not-the-author developer with subject matter expertise and one individual who has to own/execute the task. - - Troubleshooting section - case studies of previous issues - - Escalation ICs - SMEs who can be contacted if the troubleshooting and instructions are insufficient. If these people are contacted, there must be an update to the troubleshooting section even if it's just clarification of terminology. - - - Routines will be made up of pieces which fall into three general categories. - - Fully automatable routines : For these, the work is just like any other epic, and the ongoing cost is simply to ensure that the automation is still running and react to alerts. Processes added this way still need to have an entry under the wiki page named below. - - Non-automatable, simple ("highly procedural") routines: Things we can't or won't automate, but can be described by a simple flow chart and troubleshooted via any search engine, at least a majority of the time. - - Non-automatable, complex routines: Same as above but which will require significant work, consultation with subject matter experts, proof-of-concept development, etc. - - Phase 3: Tooling and telemetry implementation - Writing the automation we think we need to support the work - - Phase 4: Validation: I've left out the "rollout" phase here because this kind of process is happening whether we are methodical about it or not, whatever we do. If the task fails validation, at any time, it should be reevaluated via a new issue in the operations epic tracking the work. In general this process is something like: - - Executor of this task reads and clarifies documentation added in Phase 2 with team members before starting - - Executor then goes through some number of iterations of the process (number at their discretion), taking notes every time they have to seek help and what the answers they got seeking such help were. - - Operations team reviews the executor's notes as available and makes iterative improvements to documentation / automation. - - Executor makes the final "this is ready" call; this is not to say that there won't be more clarification / addition in the future. - - - If vendors are to be used, a "statement of work" needs to be drafted (JasoWard on DDFUN has expertise) to ensure the most productive use of vendor resources. Process documentation needs to indicate clearly what success and failure indicators for this task are critical in making this process work. - - Once deemed 'ready' to operate (generally entering Phase 4 above), ownership, a brief entry stating purpose, and links to relevant work items and process documentation will be listed under https://dev.azure.com/dnceng/internal/_wiki/wikis/DNCEng%20Services%20Wiki/107/DncEng-DncDevOps-Ownership - -- At a certain point, determined primarily by the personnel funding of the DncDevOps / FR teams, taking on new ownership will necessarily entail either simplifying (further automating / reducing scope), deprioritizing, or removing older tasks, as these tasks are expected to have a perpetual ongoing cost. - -## Examples - -The following are some sample tasks that the DncDevOps team might own on a continuing basis, with the above rubric used to justify why these would be done. At the moment these ideas mostly represent brainstorming but are listed in descending priority order. - -### Ensure all pipelines owned by the DncEng infrastructure team are kept in a passing, "green" state, with a playbook for taking action when broken. - - - **Established area?** Yes, (assuming we manage the list / dashboard for this correctly) - - **Area under active development?** Mostly no: Current epics will break the builds somewhat but that's indirect. - - **Repeating work?** Yes, it never ends. - - **Pertinent to the daily functioning?** Yes; broken builds mean lost productivity. - -### Enforce our team's OS and artifact versioning / lifetime - -Track and drive patching (both operating systems and artifacts installed thereon), end-of-life-ing dead OSes, and updating physical machines used by the team. Keep base images used by Helix clients updated on https://github.com/dotnet/dotnet-buildtools-prereqs-docker, delete end-of-life OSes from this repo, and respond to non-customer requirements for these (security updates, moving to other repos, adding new images). Communicate at least two weeks in advance (via dncpartners dl) end-of-life for Helix queues and fully remove them from support. - - - **Established area?** Yes, dotnet-helix-machines is quite old - - **Area under active development?** No epics occurring in machine management. - - **Repeating work?** Yes, it never ends. - - **Pertinent to the daily functioning?** Yes, very much. Keeping the images we use up-to-date improves security, testing (since we want to catch issues before the users), and establishing a process for this lets us uptake new functionality and versions needed by partner teams proactively. - -### Manage DncEng-owned service connections in a documented fashion (and possibly variable groups) - - - **Established area?** Yes or N/A: we've used them a long time but they're just an artifact we depend on. - - **Area under active development?** No, they are a piece of how our inter-connected AzDO / GitHub projects function. - - **Repeating work?** Since the PATs backing them always expire and require logging in as dn-bot to cycle, it never ends. - - **Pertinent to the daily functioning?** Yes; broken connection means broken builds means lost productivity. - -### Regularly find and delete vestigial objects in Azure (old image galleries, defunct services, old forgotten repro VMs, etc). Automate reporting of this. - - - **Established area?** Yes: represents all the existing deployed production work we do in Azure - - **Area under active development?** Typically not; this represents work that is deployed to production. - - **Repeating work?** Yes; users may add random new objects to Azure at any time, leading to insecurity or spending waste. - - **Pertinent to the daily functioning?** Yes, it is possible for these objects to consume limited Azure resources we expect to be reserved for what we expect to be there. Further, the objects may represent a security concern if we don't know why they're there, so knowing of their existence is very helpful. - -### Some general ideas for tasks outside of this but which would support operational quality: -- Document investigation processes via core-eng wiki or elsewhere for vendor execution in non-exceptional circumstances. -- Make templates and help communicate important changes in services provided by the DncEng team - -## Process / Rules for DncDevOps team - -The DncDevOps team will be focused on 'predictable work' - stuff that doesn't have an SLA for hours. Since the work will be largely done by the First Responders team in the beginning, the hours would likely follow that with some coordination with the Prague team when special requirements make it preferable. - -### Primary goals - -1) Inventory, monitor, and observe key pieces of .NET Core Engineering services infrastructure to keep the system in a secure, usable state -2) Communicate important changes to customers with a consistent format and pattern via email and Teams -3) Triage issues proposed to be operational using the bar above, and coordinate with the team for rejected issues outside this (goal being to not let issues "rot" whatever they are) -4) Document and offload as much repetitive / manual work as can be to vendor ICs, freeing up time for investigation and improvement. -5) Regularly engage with (and redirect where needed) customers who not seeking short-term redress to hear out their challenges / issues and represent this. Maybe hold office hours? - -### Organization - -- Unknown - this wil depend on the scope of tasks owned. Since the tasks are much more of the "continuously ongoing" type though versus responding to incoming customer pain, it makes sense to not cycle through ICs every week. I believe 1 or 2 FTE ICs and some number of vendors would likely serve the cause well. - -### Synchronization and Hand Off - -- As the First-responders team (FR) will be intimately involved with the DncDevOps team, it makes sense to unify the meeting used for both purposes. If this not acceptable by the FR lead, a regular morning-time (to allow Prague to participate) 15 minute daily Teams standup would be held. -- Whenever feasible, high-priority customer-facing work should be handed off to the First Responders team as this would be outside the DncDevOps charter. - -## Filing Issues - -- DncDevOps issues need to be created in the dotnet/core-eng repository. The usual process of filing an issue with only a link to a devdiv.vs Azure DevOps TFS work item would be followed for any issues considered "security sensitive". -- Use necessary tracking system for vendor work, which may be IcM or other non-GitHub system. -- Issues should be tagged with the "DncOp" label and added to the DncDevOps ongoing epic. - -- Guidance for issue creation - - Description of the task or bug - - Where in the code base (which repo / project/ etc) the work should be done, if known. - - Link to instructional documentation if a vendor task. - -## Notes - -This is a draft: This list is currently a set of ideas, with no specific order. Primarily, I am trying to write down things that keep the system going that either don't have a clear ownership today, or for which ownership could be transferred. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5COperations%5Cdnceng-operational-responsibilities.md)](https://helix.dot.net/f/p/5?p=Documentation%5COperations%5Cdnceng-operational-responsibilities.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5COperations%5Cdnceng-operational-responsibilities.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Operations/Helix-Machine-Management/Helix-Machine-Lifecycle-Processes.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Operations/Helix-Machine-Management/Helix-Machine-Lifecycle-Processes.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Operations/Helix-Machine-Management/Helix-Machine-Lifecycle-Processes.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Operations/Helix-Machine-Management/Helix-Machine-Lifecycle-Processes.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,281 +0,0 @@ -# .NET Core Engineering Services Operations info: - Lifecycle management for Helix Queues and .NET Core Engineering Build Images - -## Process Details - -### Glossary of terms: - -- **End of life (EOL)**: The date at which an operating system is no longer supported by its publisher. Publishers will often provide more than one timeline for this (often charging customers money for longer terms). For .NET Core's purposes, this is usally the longest possible period where security vulnerabilities will be addressed as this is what customers running .NET on these operating systems expect, as we will need to be able to continue testing the shipped product on these operating systems until the final days of their support lifecycle. - -- **Estimated Removal Date (ERD)**: The date that .NET Core Engineering Services intends to remove a test queue or build image. -This date is meant to force conversations to be had and actions to be taken, and is not meant to indicate a customer promise of .NET Core's OS support. The date may be before, or after the EOL date (preferably before) at DncEng's discretion. With the exception of the dotnet-helix-machines build where it can become an error once elapsed, this time is only used to inform warnings to users. Estimated removal dates thus can be arbitrarily extended with sufficient cause (leaving history behind in Git commits of who did it and hopefully why), but the goal is to never have unsupported and un-patch-able operating systems managed by the team. - -- **Update Required Date (URD)**: The date that .NET Core Engineering Services will next need to take action to update an image used in Helix test machines or 1ES hosted build pool images. This can be any date up to the estimated removal date, but not after it. This date is designed to allow the .NET Core Engineering Services team an opportunity to update images that need no user action (for instance: Updating to a newer, non-EOL, OS version with all the same artifacts where the users don't need to have direct communication about this.) When an existing image includes version information that makes this impossible (e.g. "19H1" is in a queue name but its corresponding Windows version is EOL) the Update Required date should be set to the same value as EstimatedRemovalDate. - -- **Matrix of Truth**: ([Epic issue link](https://github.com/dotnet/core-eng/issues/11077)) Ongoing work to provide a single source of information for operating system test matrix and life cycle for .NET Core Engineering systems. - -- **Helix Queue**: Set of machines, whether they are an Azure VM Scaleset or physical machines, which execute test work items. - -- **Build image**: Azure Compute Gallery image used to populate 1ES Pool provider instances used for all .NET Core builds. Due to executive order, we must perform all builds through images pushed to this system. - -### Summary: - -#### Why does this "process" exist in the first place? - -Previously, removing old Helix queues and images was a best-effort process. This has several major problems including: - -- It causes us to continue using images that no longer receive security patching, leading to potential attacks as well as definitely causing monitoring applications to detect these machines and cause us to react to this. -- Helix VM and on-prem machine capacity is divided amongst whatever capacity we have. If we keep around old Helix queues (on-prem or in Azure), this limits the ability to provide this capacity in still-supported OSes. -- Users would frequently only find out on rollout that their queue/image had been removed. - -#### Who benefits? -Helix users benefit from secure, patched machines and a regular communication of when what they use will be deprecated. The .NET Core product teams get the most secure and accurate-to-real-users'-machines images (that is, containing all the recent patches and updates to operating systems and components which could affect test / product behaviors) possible to run their tests or builds on. - -#### What happens if it doesn't happen? -Without a regular process, we will be bogged down with responding to alerts for VMs or other resources using 'old' images, and even ignoring any security-related implications (reasonable for most Helix test machines, less so for build images), we will continue to pay for storage and compute costs for no-longer-supported operating systems. - -### Process Boundaries - -- Related repositories: - - https://dnceng.visualstudio.com/internal/_git/dotnet-helix-machines --> Source control for all Helix VMs, 1ES Pool provider images, and Helix on-premises machine setup functionality - - https://github.com/dotnet/dotnet-buildtools-prereqs-docker --> Source control for a wide variety of docker images automatedly published to Microsoft Container Registry (MCR). Images used for testing Helix "docker" images come from here. Work will eventually be done to move most of this image generation into the dotnet-helix-machines repo. -- Task scope: - - - In scope: - - Monitor [dotnet-helix-machines](https://dnceng.visualstudio.com/internal/_build?definitionId=596) pipeline and respond to warnings / errors produced by EstimatedRemovalDate and UpdateRequiredDate times elapsing by either updating the images referenced and/or extending these dates, or removing these queues. - - Whenever extending a date further out than the lifespan of an existing operating system, comments should be added above this (or at least in commit messages) explaining the extension. - - Coordinate with rollout owners to ensure that upcoming removals and image refreshes (any queue that has produced a warning in the dotnet-helix-machines "Validate" stage) are included in rollout status emails. - - On a monthly cadence, review and update the EstimatedRemovalDate / UpdateRequiredDate values of images generated by the dotnet-helix-machines repository for accuracy. Use .NET release PM team and the internet for deciding dates. - - - Not in scope: - - Deciding the OSes for which we will provide images. (tracked by https://github.com/dotnet/arcade/issues/8832) - - Patching of Operating systems or updating of artifacts (tracked by https://github.com/dotnet/arcade/issues/8813) - -- Contacts for non-owned parts of the process: For external ownership, who can we talk to? - - For end-of-life operating systems, the release PM team owns the final word of when we can actually get rid of support. Certain operating systems can and will be maintained outside their publicly-communicated lifespan, usually owing to some important customer need. The release PM team is composed of Jamshed Damkewala (jamshedd), Rich Lander (rlander), Lee Coward (leecow), and Rahul Bhandari (rbhanda); contact them for any questions in this space. Some examples of operating systems we support beyond their normal end-of-life include Ubuntu 16.04 and Windows 7 / Server 2k8R2, but there will be more exceptions. - - DncEng "matrix of truth": IlyaS, general SME : MattGal - -### Process Inputs / Outputs - -#### SME information: - -Descriptions of what/where the inputs to the process come from (the answer to "what do I or the automated process neeed to consider to perform this task?"), and what performing the below steps correctly achieves ("what comes out the other side?") - -Inputs: -- The OS "Matrix of Truth" (future output from https://github.com/dotnet/arcade/issues/8832). Until this matrix exists, what we have is considered "in matrix" but we need to be removing unsupported OSes. -- The .NET Core release team's input (they receieve notifications for OS removals from build/test support via the partners DL, and can veto this with justification) -- EstimatedRemovalDate warnings / errors from the dotnet-helix-machines-ci pipeline - -Outputs: -- (Where needed) Removal of Helix image definition from dotnet-helix-machines repository. -- (Where needed) Updates of Helix base image or image references in the dotnet-helix-machines repository, or tracking issues for this work -- Communication with the release PM team ensuring any "first time" removals of a given OS are acceptable; this team may veto this and will provide new estimated removal dates if they do - - Once the "Matrix of Truth" epic is complete and this is approved by the release PM team, we may consider no longer notifying them) - - If not removing all instances of an OS at once (e.g. if removing build images while leaving test queues), mark the remaining instances of the OS with a comment indicating removal has already been approved so this step may be skipped -- Communication blurb following the below template in weekly rollout email. As these warnings are designed to appear in the dotnet-helix-machines official build starting 3 weeks prior to expiration, it should consistently allow the current week's rollout (or "not rolling out" mail) to indicate that users will soon need to take action. - -#### Example Communication - -The following Helix Queues and/or Build images will be removed on the Wednesday rollout following the estimated date. Please remove usage of these queues/images before this date to keep your pipelines and tests functional. - -Helix Queues: - -| Queue Name | Estimated removal date | -| - | - | -| Some.Helix.Queue | 03/14/2022 | -| Some.Other.Helix.Queue | 03/10/2022 | - -1ES Hosted Pool Images - -| Image Name | Estimated removal date | -| - | - | -| Build.OperatingSystem.KindOfImage | 03/21/2022 | -| Build.DifferentOperatingSystem.KindOfImage | 03/22/2022 | - -Removing no-longer-supported operating systems on a regular cadence both allows us to be as secure as possible and use more of the resources we have available more for still-supported platforms. -If you feel this removal is in error, or believe a specific expiration should be extended, please email dnceng@microsoft.com with your concerns. - -### Execution Steps - -#### Fully-automatable routines: -- EstimatedRemovalDate & UpdateRequiredDate notifications - Part of the dotnet-helix-machines-ci pipeline, these must be both in the future and estimated removal date be >= update required date or the build will fail. -- Links: - - Documentation: https://dnceng.visualstudio.com/internal/_git/dotnet-helix-machines?path=/definitions/readme.md - - Pipeline: https://dnceng.visualstudio.com/internal/_build?definitionId=596 - - ImageFactory Wiki (includes access instructions): https://dev.azure.com/devdiv/XlabImageFactory/_wiki/wikis/XlabImageFactory.wiki (includes access instructions) - - .NET PM team's OS Version Management calendar: https://dev.azure.com/devdiv/DevDiv/_wiki/wikis/DevDiv.wiki/12624/OS-Version-Management-Calendar-2022 (Adjust year for the current year) - -- Known issues impacting the area: None -- Known tech debt: [Matrix of Truth](https://github.com/dotnet/arcade/issues/8832) work is not complete yet; once this is done we need to make sure this integrates into the existing system for update/removal. - -#### Manual processes: - -##### Daily: - -- Review [the day's main pipeline executions](https://dnceng.visualstudio.com/internal/_build?definitionId=596&_a=summary&repositoryFilter=3&branchFilter=152037) -- If any warnings about EOL queues arise (see guidance for specific types below): - - Check whether this removal represents the "last" of this operating system (no other queues have this OS). If so, get confirmation from the Release PM team or confirmation from “Matrix of Truth” to ensure its removal is acceptable and mention this in issues. - - If removal is deemed inappropriate, make pull requests to the dotnet-helix-machines repo extending the time to a new, agreed-upon, date. - - If pull requests are created, monitor subsequent builds in the pipeline until it has succeeded; - - Open issues in the dotnet/arcade for all actions, with the "First Responder" tag. Add to list of queues for end-of-week update. - - Create pull requests removing these test or build images after the current week's rollout in the week they will be removed, and follow these until merged. - -- If any warnings about "Update-required" images arise: (e.g. "`##[warning] has update-required date: YYYY-MM-DD, in the next three weeks. Please either update the image to newer, file an issue requesting this, or extend the date with a comment explaining why if no action is taken.`") - - Check whether updated images exist: - - Refer to the yaml for these images, found under the "[definitions](https://dnceng.visualstudio.com/internal/_git/dotnet-helix-machines?path=/definitions)" folder of [the dotnet-helix-machines repo](https://dnceng.visualstudio.com/internal/_git/dotnet-helix-machines). Some windows images may be found in [definition-base\windows.base.yaml](https://dnceng.visualstudio.com/internal/_git/dotnet-helix-machines?path=/definition-base/windows.base.yaml) - - Find the image referenced in the yaml, or directly inside definitions\shared in the dotnet-helix-machines repository. - - Run the osob CLI. The following commands assume you have .NET Core runtime on the path and are inside the `tools\OsobCli` folder of this repository. - - `dotnet run list-image-versions -l westus -d ..\..\definitions` - - `dotnet run list-imagefactory-image-versions -d ..\..\definitions` - - Images with newer versions available will look like: -``` -For : - Current version: - Latest available version: - ** Upgrade! ** -``` - - If there are updated versions of the image: - - Wherever "Current Version" and "Latest Version" do not match, modify the image version to match the version printed out by the OSOB CLI tool above - - Set the new `UpdateRequiredDate` to 90 days after the previous date, or the `EstimatedRemovalDate`, (whichever comes first). - - Create a pull request with these changes and follow until merged. - - If there are no updated versions of the image, simply set the update-required date to 90 days past the previous one - - After merging any pull requests where multiple images are updated, as usual monitor "main" branch builds until they are successful. - -- If the main build has failed (red): - - Ping [DncEng First Responders Teams Channel](https://teams.microsoft.com/l/channel/19%3aafba3d1545dd45d7b79f34c1821f6055%40thread.skype/First%2520Responders?groupId=4d73664c-9f2f-450d-82a5-c2f02756606d&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) and ask for next steps. - - Optionally, create issue in dotnet/arcade with the "First Responders" label requesting investigation. - -##### Weekly (Currently, most rollouts are on Wednesdays): -- Find the rollout pipeline. Rollout occurs around 9 AM in the [dotnet-helix-machines-ci pipeline](https://dnceng.visualstudio.com/internal/_build?definitionId=596&_a=summary) with branch 'production'. Contact the dnceng team if this is not occurring or you lack permissions. -- Even if there are no tasks to be done, always create an issue using [this template](https://dnceng.visualstudio.com/internal/_workitems/create/Task?templateId=7599e1ed-4c83-45cd-ad97-10ce36dbbb20&ownerId=3f024d6c-9884-4f38-a598-025fca9dfcd2) and put it into the "active" state. -- Fill out the sections of the template: - - Link to completed pipeline - - Status of the rollout (completed normally, aborted, rolled back, etc) - - List of any images/queues producing warnings in the pipeline (errors will stop us from running it) - - Create new issues in [dotnet/arcade](https://github.com/dotnet/arcade/) identifying the queue and linking to the build. Include links to these issues under the "queues that will expire in the next 7 days" or "after the next 7 days" headings as appropriate. -- When all the above is complete for a given week, you may close the issue. - -##### Monthly (closest business day to the 15th): -**Request DDFUN updates for on-premises Helix machines** - -We depend on our partner team DevDiv Fundamentals ("DDFUN") for machine maintenance tasks. DDFUN vendors use table "OsobInventory" in storage account 'helixscripts2' to view the machines. We want any machine that shows up in HelixEnvironment "Production" with Partition Key not equal to "Not in Helix" to be at least reviewed for update monthly. See @MattGal or @Ilyas1974 if you believe you need to gain read access to this table. If new queues are found in the table that are not below, please make a pull request to update the list. - -We need to ensure and drive that the machines we are running are updated to the latest possible versions. Since we cannot rely on AzSecPack and automated reporting for these updates, we'll generate IcM tickets requesting this. For each of the three categories of operating system below (Linux, MacOS, and Windows) please create an IcM ticket [here](https://ddfunlandingpagev120210311150648.azurewebsites.net/) using the "[Other](https://portal.microsofticm.com/imp/v3/incidents/create?tmpl=E3619N)" template. - -After creating such an IcM for each of the three groups of machines, use [this template](https://dnceng.visualstudio.com/internal/_workitems/create/Task?templateId=5ad0c1bc-5e95-45a1-b40b-de81c12b5b4a&ownerId=3f024d6c-9884-4f38-a598-025fca9dfcd2) to link the three IcM tickets, and keep the issue open until all three are resolved. - - -Suggested IcM Description: -``` -Machines in the following queues need to be updated to their latest patch versions. - - - -- No major version updates should occur in operating systems (e.g. do not allow a Windows 10 system to update to Windows 11, or OSX 10.15 to update to 11.0/12.0) -- For windows, Windows update should be executed until it stops prompting for changes. -- For MacOS, use the provided UI to take system updates while remaining in the same release band. -- Where possible (use judgment), if linux package managers have recommended updates these should be taken. -``` - -Linux machines (raspbian 9 iot devices should NOT get updated) - -- alpine.amd64.tiger.perf -- ubuntu.1804.amd64.owl.perf -- ubuntu.1804.amd64.tiger.perf -- ubuntu.1804.amd64.tiger.perf.open -- ubuntu.1804.arm64.perf -- ubuntu.1804.armarch -- ubuntu.1804.armarch.open - -MacOS Machines - -- osx.1015.amd64 -- osx.1015.amd64.appletv.open -- osx.1015.amd64.iphone.open -- osx.1015.amd64.iphone.perf -- osx.1015.amd64.open -- osx.1100.amd64 -- osx.1100.amd64.appletv.open -- osx.1100.amd64.open -- osx.1100.amd64.scouting.open -- osx.1100.arm64 -- osx.1100.arm64.appletv.open -- osx.1100.arm64.open -- osx.1200.amd64.iphone.open -- osx.1200.amd64.open -- osx.1200.arm64 -- osx.1200.arm64.open - -Windows Machines - -- windows.10.amd64.19h1.tiger.perf -- windows.10.amd64.19h1.tiger.perf.open -- windows.10.amd64.20h2.owl.perf -- windows.10.amd64.android.open -- windows.10.amd64.galaxy.perf -- windows.10.amd64.pixel.perf -- windows.10.arm32 -- windows.10.arm32.iot -- windows.10.arm32.iot.open -- windows.10.arm32.open -- windows.10.arm64 -- windows.10.arm64.appcompat -- windows.10.arm64.open -- windows.10.arm64.perf -- windows.10.arm64.perf.surf -- windows.10.arm64.tof -- windows.10.arm64v8.open -- windows.11.amd64.cet -- windows.11.amd64.cet.open - -##### Possible issues you may encounter: - -When the dotnet-helix-machines-ci build to be rolled out fails with `{QueueOrImageName} has estimated removal date: {c.EstimatedRemovalDate}, which has elapsed. Either extend this date in the yaml definition or remove it from usage to proceed.`: - -1. Review the date and confirm using internet search / .NET Release PM Team calendar. **Note the "Matrix of Truth" data, once it exists, supersedes any data in "Estimated Removal dates" -2. If the date is incorrect, consult with DncEng Operations v-team (or, use judgment) to determine a new date and extend it via pull-request to this repository -3. If the date is valid, remove the definition via pull request to this repository. If we have erroneously never communicated this state, you may use discretion to set a date sometime in the future to keep the current status quo, but please include a comment over the definition explaining why. - - -When the build to be rolled out contains warnings with `{QueueOrImageName} has estimated removal date: {Date}, in the next three weeks. Please include this in communications about upcoming rollouts.`: - -1. Review the date and confirm using internet search / .NET Release PM Team calendar and release notes. Example: [.NET Core 6.0 release notes](https://github.com/dotnet/core/blob/main/release-notes/6.0/supported-os.md) -2. If the date is incorrect, consult with DncEng operations v-team to determine a new date. -3. If the date is valid, work with the .NET Engineering Services Rollout team ([Teams Channel](https://teams.microsoft.com/l/channel/19%3a72e283b51f9e4567ba24a35328562df4%40thread.skype/Rollout?groupId=147df318-61de-4f04-8f7b-ecd328c256bb&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47)) to ensure that this is communicated with every partners "rollout" email until the removal has occurred. - -The similar warnings/errors for Update Required look like the below. Their only real difference is that these updates only are shared with the rollout team / customers in the case where a "significant" change occurs (e.g. updating the semi-annual Windows version). - -`{QueueOrImageName} has update-required date: {Date}, which has elapsed. Either extend this date in the yaml definition (add comments if relevant), or remove it from usage to proceed.` -`{QueueOrImageName} has update-required date: {Date}, in the next three weeks. Please either update the image to newer, file an issue requesting this, or extend the date with a comment explaining why if no action is taken.` - -#### Known issues impacting the area: -- We regularly have difficulty generating "novel" new images; when this occurs whoever is driving the process should extend dates (with comments why) as proactively as possible, since we'd like to minimize communication to users that implies actions are needed on their part if we know we don't have something for them to upgrade to. -- Known tech debt: Completion of "Matrix of Truth" work (needed for unified tracking of expiration dates) -- Troubleshooting guide per-step, ideally tested by execution by an individual unfamiliar with the feature area(s) involved: None for now, can add as users hit issues. - -#### Troubleshooting: - -- Ensure all EstimatedRemovalDate/UpdateRequiredDate values are expressed in YYYY-MM-DD format. -- Use DncEng team for guidance when investigating errors - -### Validation Steps - -After completing manual steps: (cadence TBD, but probably weekly before rollouts), perform the following checks and make a note in https://dev.azure.com/dnceng/internal/_wiki/wikis/DNCEng%20Services%20Wiki/512/Helix-Machine-Management-Operations-Notes - -Template (insert at top of wiki mentioned abov) -``` -Date: -Executor of manual checks: (Github or MS alias) -Link to Production rollout pipeline: - -Pipeline state: -- Monitor the next dotnet-helix-machines pipeline execution. No warnings or errors related to EstimatedRemovalDate time elapsing should be seen. Provide a link to this pipeline execution. Validate that all queues expected to be deleted did actually get deleted (this can be seen in the "Run DeployQueues" step of the Deploy Queues job). -- Monitor First Responders Teams channel for surprised users; in the case of erroneous deprecation, work with DncEng team for a hot fix. In the case of expected removal, use discretion and help unblock the user. - -Notes: -- Anything interesting or unusual that happened as part of this week's check-in. -- Issue(s) falling out of the process for this week: -``` - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5COperations%5CHelix-Machine-Management%5CHelix-Machine-Lifecycle-Processes.md)](https://helix.dot.net/f/p/5?p=Documentation%5COperations%5CHelix-Machine-Management%5CHelix-Machine-Lifecycle-Processes.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5COperations%5CHelix-Machine-Management%5CHelix-Machine-Lifecycle-Processes.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Operations/Templates/process-info-template.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Operations/Templates/process-info-template.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Operations/Templates/process-info-template.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Operations/Templates/process-info-template.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,84 +0,0 @@ -# .NET Core Engineering Services Operations info: - {Process Name} - -## Process Details - -### Summary: -Why does this "process" exist in the first place? Who benefits? What happens if it doesn't happen? - -### Process Boundaries - -What is, and isn't part of this process? Provide data that provides boundaries both in terms of code base and time. As this is a template, feel free to remove / add as applicable. If you find yourself adding content that should be global, please make a pull request to this template doc. - -- Related repositories: Links to relevant repos where work will be done -- Task scope: List guidance/examples of in-scope and out-of-scope for the task -- Contacts for non-owned parts of the process: For external ownership, who can we talk to? - -### Process Inputs / Outputs - -Descriptions of what/where the inputs to the process come from (the answer to "what do I or the automated process neeed to consider to perform this task?", and what performing the below steps correctly achieves ("what comes out the other side?") - -Examples: - -Inputs: -- Base Docker images from DockerHub.io / mcr.microsoft.com -- Gallery Images available from the Azure Portal -- Package versions from public / internal NuPkg feeds -- State of the objects in an Azure Subscription - -Outputs: -- Updated dependencies / images -- Changed state of -- Assorted reports or telemetry used in reporting - -### Execution Steps - -These steps will vary based off the type of operation involved. As your process may use any combination of automated / manual stages, use what you need from the template and delete - -#### Fully-automatable routines: -- Description of the process -- Links to: - - Source code, any other (say, in-repo) documentation for the automation - - Pipelines associated with build / deployment of relevant components. - - Telemetry pages / Grafana alerts related to this process -- Known issues impacting the area -- Known tech debt that may cause validation "blindness" - -#### Manual processes: -- Step-by-step description of the process in sequential markdown list format. -- Known issues impacting the area -- Known tech debt that may cause validation "blindness" -- Troubleshooting guide per-step, ideally tested by execution by an individual unfamiliar with the feature area(s) involved. - - -#### Troubleshooting: - -List of what to do when "known" things go wrong. When a new problem occurs and requires investigation and fixing, it should be added here. - - -### Validation Steps - -After completing manual steps, or on some regular cadence (to be determined), list any follow up checks/activities that need to be done, including things like: - -- Which build(s) need to be in a green state -- Sites to check -- "Smoke testing" steps for functionality known to lack automation/ have historical regressions - -#### Checklist for reviewing this document - -This part is more for guidance but can be retained in documents deriving from the template; it gives the writer a means to try to warn against any recurring problems seen. - -- Have the supplied steps been executed by a non-SME, non-author IC? - -- Do any references to resources include how to obtain and which security permissions are required (if any) - -- Are links pointing to other documents or locations valid? - - Will they be readable by the target audience? If restricted, do they tell the reader where to gain access? - - Will they continue to exist in the future? (some links, like non-retained AzDO builds, are impermanent) - -- Is/are there at least one (ideally two) SME IC(s) listed as contact for clarification? - -- Does the document specify sufficient detail that an artibrary reader would be able to reason about and execute the processes described? - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5COperations%5CTemplates%5Cprocess-info-template.md)](https://helix.dot.net/f/p/5?p=Documentation%5COperations%5CTemplates%5Cprocess-info-template.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5COperations%5CTemplates%5Cprocess-info-template.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Policy/ArcadeContributorGuidance.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Policy/ArcadeContributorGuidance.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Policy/ArcadeContributorGuidance.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Policy/ArcadeContributorGuidance.md 2023-11-13 13:20:34.000000000 +0000 @@ -1,5 +1,5 @@ # Contributor Guidance for Arcade -Over and above the [.NET Core contributor guidelines](https://github.com/dotnet/coreclr/blob/master/Documentation/project-docs/contributing.md) (which are important), there are some principles and guidelines that are specific to Arcade as well. +Over and above the [.NET Core contributor guidelines](https://github.com/dotnet/runtime/blob/main/CONTRIBUTING.md) (which are important), there are some principles and guidelines that are specific to Arcade as well. For the most part, contributions to Arcade are straightforward and relatively smooth. However, from time to time, getting changes in can be challenging, and even frustrating. The very nature of Arcade is that it's shared across multiple teams. This document attempts to clarify some of the expectations as well as provide some 'advice' for success when contributing to Arcade. @@ -20,8 +20,6 @@ ### Ownership The current owner for dotnet/arcade is Mark Wilkie . The current point persons are: -- Alex Perovich @alexperovich / -- Jon Fortescue @jonfortescue / - Michael Stuckey @garath / - Ricardo Arenas @riarenas / diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Policy/TeamProcessPolicy.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Policy/TeamProcessPolicy.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Policy/TeamProcessPolicy.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Policy/TeamProcessPolicy.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -# Team Process Policy - -- For all work done, a GitHub issue must exist, this includes, but not limited to: - - Every pull request should reference back to a GitHub issue (if the PR is in AzDO, include a link to the GitHub issue in the PR details). - - Every rollout must have a GitHub issue to document the change to production. - - Every hotfix and deployment rollback must have a GitHub issue to document the change to production. -- All dependency additions/changes, such as version upgrades must be approved through team management (e.g. Application Insights) - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CPolicy%5CTeamProcessPolicy.md)](https://helix.dot.net/f/p/5?p=Documentation%5CPolicy%5CTeamProcessPolicy.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CPolicy%5CTeamProcessPolicy.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Policy/TechnicalDebtPolicy.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Policy/TechnicalDebtPolicy.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Policy/TechnicalDebtPolicy.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Policy/TechnicalDebtPolicy.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# Driving technical debt principles: -1. We must not add new technical debt. (unless there’s an explicit business decision to do so) -1. We must reduce our current debt – reasonably over time. - -It should be said that it’s easy to miss some of this stuff, so we should feel free to hold each other accountable in code reviews. In fact, getting more active participation in code reviews across the board would be fantastic. - -## Stop increasing technical debt: -We need do more than just say “don’t add debt”. We don’t always know what this means, we have business pressures which make this difficult at times, and we don’t necessarily know where to start. Actually doing this requires a combination of concrete changes in policy, and continued cultural changes. (yes, they’re intertwined…) But the good news is that we’re ready as a team to tackle this together. - -### Policy: (automation of checks wherever we can will be important for sustainability) -- The following need confirmation from another senior dev. (see ‘ getting confirmation’ below for details) - - New dependencies (for example, this leads to inconsistencies within the project – making for a mess) - - New end points or services - - Duplication of code - - New language or language version - - Use of a prelease service/library -- Appropriate test coverage - - Make it more obvious the extent of test coverage. For example, show code coverage percentage as part of the PR results. - - An initial bar of at least 50% code coverage –80% better. Exceptions can be given as appropriate. (either up or down) The point is to get the right coverage, not just get a number…. (For example, see here for our current code coverage) -- Documentation should exist (to say the obvious, we still need better clarity on how we document in general – but that’s outside the scope of this email) - - It needs to be usable by the intended customer - - It needs to be discoverable - - Appropriate existing documentation updated (remember this is debt as well) -- Features need a design “one pager” and confirmation from another senior dev - - Bug fixes and/or small items don’t need this - - For now, let’s put design into the epic itself so we can always find it - -### Cultural Changes -- Brown bag at least once a year to recount war stories. The goal is to keep the “why” top of mind. -- Retrospectives where/when appropriate. -- General management support (including active participation) for encouraging/enforcing this new policy on our PRs. It’ll hurt some at times… -- Encourage conversations about how we keep our quality up, debt low, and general improve our technical offerings. -- Discuss in our monthly team meeting from time to time - -### Getting Confirmation for a change/proposal -- Email ‘dnceng’ for broad awareness -- Get at least one other senior dev to ACK -- It should be noted that we’ll make mistakes. We can only make decisions based on what we knew at the time. The expectation is less about building zero debt, but rather changing our culture – e.g. the way we think about these things. - -## Reduce existing technical debt: -It would be unreasonable to do nothing other than pay down our technical debt as we have a business to run as well. However, we can focus on a specific area and make a big impact. - -### Policy: -- There should always be a business priority on the board that actively focuses on reducing technical debt. (right this minute we have two, Helix, and Validation) -- We should limit debt reduction business priorities to one at a time. This is to help stay away from “peanut butter” (spreading effort over a wide area) and make significant progress reducing debt in one area. It’s sorta like choosing a credit card balance with the highest interest to pay down first. -- There may be times where the business dictates that we don’t have a debt reduction business priority on the board. However, this should be the exception and requires M2 approval as it would go against one of our core principles. For a review, remember to check out our guiding principles. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CPolicy%5CTechnicalDebtPolicy.md)](https://helix.dot.net/f/p/5?p=Documentation%5CPolicy%5CTechnicalDebtPolicy.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CPolicy%5CTechnicalDebtPolicy.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/agentless-helix.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/agentless-helix.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/agentless-helix.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/agentless-helix.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,55 +0,0 @@ -# Allow for waiting for helix to use agentless tasks - -## Overview -We want to reduce wasted compute caused by build jobs waiting for helix tests to complete. To do this we can use the "agentless job" feature in azure pipelines to remove the machine that waits for the helix job to be finished. - -## Stakeholders -- Our Budget -- Customers who want to retry test jobs without having to rebuild - -## Pros -- Build machine time is no longer wasted on waiting for helix jobs -- Test job retry doesn't require rebuilding the tests - -## Cons -- Msbuild no longer waits for the helix jobs, so code written in msbuild to process job results needs to either go away or be rewritten server side. - -## Current Workflow -The current build jobs that run helix work do the following: -1. Acquire build agent from pool -1. Compile code and create helix payloads and job list -1. Start azure pipelines test runs -1. Send Job to Helix via rest api -1. Poll rest api periodically to check status -1. Once job reports finished, verify results and finalize test runs -1. Return build agent to pool - -This workflow holds on to a build agent for a long period where it is doing nothing but polling a rest api for status, this period can be removed. - -## New Workflow -Using agentless jobs, we can change this flow to the following: -1. Acquire build agent from pool -1. Compile code and create helix payloads and job list -1. Save job information to output variable -1. Return build agent to pool -1. In agentless job: - 1. Read output variable from previous job/stage and send request to helix api using [Invoke Rest Api](https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/http-rest-api?view=azure-devops) - 1. Using the "Callback" completion mode agentless job waits for notifications from helix service -1. In Helix Service - 1. Receive request from agentless job and store information - 1. Start test runs - 1. Start helix job execution - 1. Wait for job completion - send progress logs back to azure pipelines - 1. Report test results for workitem statuses - 1. Finish test runs and check for job pass/fail status - 1. Report task completion to azure pipelines -1. Agentless job finishes after receiving notification from helix service - -This workflow gives back the build agent to the pool shortly after finishing the build, and allows the helix jobs to run without requiring a build agent to be listening. -The agentless job can also be retried to re-run the helix job(s) without requiring a rebuild. - -## Proof of Concept -I have a proof of concept in this PR https://github.com/dotnet/arcade/pull/10342. The agentless job can be seen running here https://dev.azure.com/dnceng-public/public/_build/results?buildId=55561&view=logs&j=830c6850-7aa7-5384-6c90-1a1a71217f4b. - -## Implementation details -This solution requires a web api endpoint on our server that handles the request from the agentless job, then a service running inside our cluster that starts and monitors jobs based on these requests. This service will need to fixup the job payloads with test run information, then monitor execution and report status back to azdo. Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/assets/GrafanaAlert.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/assets/GrafanaAlert.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/assets/MachinesOffline.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/assets/MachinesOffline.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/assets/MultipleSubs.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/assets/MultipleSubs.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/assets/QueueCoreSetup.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/assets/QueueCoreSetup.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/assets/QueueSecurityBuild.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/assets/QueueSecurityBuild.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/assets/RedirectJobsWorkflow.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/assets/RedirectJobsWorkflow.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/assets/Telemetry.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/assets/Telemetry.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/assets/toolversions.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/assets/toolversions.png differ diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/AutoRetryDocumentation.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/AutoRetryDocumentation.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/AutoRetryDocumentation.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/AutoRetryDocumentation.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ -# Documentation for Auto Retry of Failures in Helix -The following document shows the user documentation of the auto retry feature of test failures in helix according to the [requirements](https://github.com/dotnet/arcade/blob/main/Documentation/Project-Docs/Auto-Retry%20Failures/Overview-Requirements.md). - -# What is Auto Retry? -When a Test fails in Helix, Auto Retry feature provides the ability to automatically retry the test until it passes or crosses the number of retries specified by the user and communicate to the user that the test has passed only after retrying in Mission Control. - -# How to configure Auto Retry? -By adding the optional property (MaxRetryCount) to the MSBuild definition of a repo is all that is needed to be done to turn Auto Retry on. -For eg. -For CoreFx, Add ` (Value of allowable retries) ` under the `` in https://github.com/dotnet/corefx/blob/master/src/upload-tests.proj#L11-L79. Once the build picks up the optional property, tests will automatically requeue failed work items to be retried until the MaxRetryCount is reached / the work item passes. - -# Mission Control -In order to see the information in [Mission Control], the user must **Log in**, otherwhise no information will be available. - -If a work item passed on a retry, the information will be displayed by default along with the count of items that had passed on retry. -![](./Images/WorkItemAggregateSummary_Count.JPG?raw=true) - -Each Work Item that was passed on retry will display with a retry icon and tool tip showing `Intermittent Failures` -![](./Images/WorkItemAggregateSummary_Icon.JPG?raw=true) - -Each attempt of retry details can be viewed from the Tests Details Page. -![](./Images/Logs.JPG?raw=true) - -# How to give feedback? -Please create a new issue in the [core-eng](https://github.com/dotnet/core-eng) repository. - -[Mission Control]: https://mc.dot.net/#/ - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CAuto-Retry%20Failures%5CAutoRetryDocumentation.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CAuto-Retry%20Failures%5CAutoRetryDocumentation.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CAuto-Retry%20Failures%5CAutoRetryDocumentation.md) - diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/AutoRetryTelemetry.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/AutoRetryTelemetry.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/AutoRetryTelemetry.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/AutoRetryTelemetry.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -# Overview -This document provides Kusto Queries to pull telemetry data from Kusto DB for Auto-Retry of Test Failures. - -## WorkItems that belong to Jobs that have total pass and has items / tests that passed on retry: - -``` -let jobs = Jobs -| where Source == [source_name] -| where TestsFail == 0 and ItemsPassedOnRetry > 0 and TestsPassedOnRetry > 0 -| project JobId; -WorkItems -| where Status == 'PassOnRetry' -| where JobId in (jobs) -| summarize count(WorkItemId) by FriendlyName -| project FriendlyName, count_WorkItemId -``` -## Tests Passed on Retry summarized by type and date: - -``` -Jobs -| where Source == [source_name] -| summarize count(TestsPassedOnRetry) by Type, format_datetime(Finished,"yyyy-MM-dd") -``` -## Tests Passed on Retry summarized by type and Month: - -``` -Jobs -| where Source == [source_name] -| summarize count(TestsPassedOnRetry) by Type, format_datetime(Finished,"yyyy-MM") -``` -## Test Results for a specific WorkItemFriendlyName, Type, Method, Arguments, if the test had failed and passed on retry. -Test Results table has huge amount of data so getting pass on retry data for entire set of tests/workitems will break the DB’s back, hence the need to filter by a specific test. - -``` -TestResults -| where WorkItemFriendlyName == [WorkItemFriendlyName] - and Type == [Type] - and Method == [Method] - and Arguments == [Arguments] - and Result != 'Pass' -| summarize arg_max(toint(Attempt), *) by WorkItemId, Type, Method, Arguments, ArgumentHash -| join kind = leftouter( - TestResults - | where WorkItemFriendlyName == [WorkItemFriendlyName] - and Type == [Type] - and Method == [Method] - and Arguments == [Arguments] - and Result == 'Pass' - ) on WorkItemId, Type, Method, Arguments, ArgumentHash -| extend PassOnRetry = isnotnull(WorkItemId1) -| summarize count() by Type, Method, Arguments, Result = iif(PassOnRetry, 'PassOnRetry', Result) -``` -### Sample Value for Parameters: - -[source_name] : 'official/corefx/master/' - -[WorkItemFriendlyName] : 'System.Net.Http.WinHttpHandler.Functional.Tests' - -[Type] : 'System.Net.Http.WinHttpHandlerFunctional.Tests.WinHttpHandlerTest' - -[Method] : 'SendAsync_SlowServerAndCancel_ThrowsTaskCanceledException' - -[Arguments] : '' - - - - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CAuto-Retry%20Failures%5CAutoRetryTelemetry.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CAuto-Retry%20Failures%5CAutoRetryTelemetry.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CAuto-Retry%20Failures%5CAutoRetryTelemetry.md) - Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/Images/Logs.JPG and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/Images/Logs.JPG differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/Images/WorkItemAggregateSummary_Count.JPG and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/Images/WorkItemAggregateSummary_Count.JPG differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/Images/WorkItemAggregateSummary_Icon.JPG and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/Images/WorkItemAggregateSummary_Icon.JPG differ diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/Overview-Requirements.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/Overview-Requirements.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/Overview-Requirements.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Auto-Retry Failures/Overview-Requirements.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,68 +0,0 @@ -# Overview -The requirement is to have an ability to retry Helix Test Failures automatically instead of a developer hunting the offensive test and having to manually queue a rerun or spam the PR's with "[at] dotnet-bot test this please". -In order to promote always green before merging, a mechanism to manage inherently flaky tests in a highly visible fashion is needed. Some tests (like networking) perhaps are flaky by design, but others require additional dev attention. Regardless, the infrastructure should provide the ability to not fail the entire run - but rather provide visibility to the fact that certain tests required multiple tries to pass. - -The following are the Steps to achieve this at a high-level: -1. Ability to configure at a job level: - - If a work item can be retried on failure (defaults to false) - - Max retries allowed for a workitem (defaults to 0) -1. Helix Client detects a failed workitem and requeues the work item at the end of the queue. A work item is not a test. -1. Helix API will retry the work item until it passes or max retry count is hit. -1. Test results for each rerun is stored in DB for further retrieval. -1. On success or when the max retries allowed count is reached the work item is marked as WorkItemFinished. -1. MC displays the retried workitem with a specific color/format (TBD). The Tests that passed on retried displayed with a specific icon (TBD) -1. MC displays logs for each retry. -1. MC displays number of retries - -# Out of scope -1. There will be no attempt to retry a work item on timeouts. - -Detailed requirements list below. - -# Helix Client - -1. Support a separate parameter / reuse delivery count in QueueInfo to identify if a particular work item is a retry work item. -1. Requeue a work item if one or many tests fail in the work item. -1. Identify if Min(Max Delivery Count for Requested Queue - 1, Requested Value for retries) has crossed. -1. Retry work item requeued with a special event other than work item started. -1. WorkItemFinished should not be sent when requeuing a workitem until the workitem succeeds or max retires for the workitem has reached. - -# Helix Controller - -In order to support automated configurable retry, we'll need a few changes in the controller: - -1. In Job Started V2+ messages, have a new optional property, call it MaxRetryCount or MaxDeliveryCount. (the difference between the two is 1) -1. When the property is not supplied, set it to 0 (for MaxRetry option) or 1 (for MaxDelivery) -1. When the property is supplied, set it to Min(Max Delivery Count for Requested Queue - 1, Requested Value) -1. Attach the property of your choice to the work items being sent to the clients. - -### Nice to Have -1. Optional: Send App Insights Trace every time you have to pick a value < than requested (helps investigations for folks curious about delivery counts.) - -# Helix API - - 1. Helix API needs to be aware of partial failures. - 1. Helix API needs to be able to process multiple test results for a work item ie change the 1 to 1 mapping from name to result blob to potentially a list. - 1. Helix API notifies WorkItemFinished to CI only after the retries are finished. No change to CI is needed. - -# DB - -1. Currently we check to pick the latest set of results for a workitem, this check needs to be removed. - -# Mission Control UI - -1. Add a new icon to show that the test passed on a retry / test failed despite reruns. TBD what icon needs to be used. -1. Show multiple entries of logs for retried workitems. -1. "Show Failures Only" checkbox should display the retried tests as well. -1. Show number of retries, we are already displaying number of failures and skips - -### Nice to Have -1. Separate Checkbox - Show retried workitems (TBD the name) should display only the retried work items - - - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CAuto-Retry%20Failures%5COverview-Requirements.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CAuto-Retry%20Failures%5COverview-Requirements.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CAuto-Retry%20Failures%5COverview-Requirements.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/AutoScaler/AutoScalerInvestigateIssues.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/AutoScaler/AutoScalerInvestigateIssues.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/AutoScaler/AutoScalerInvestigateIssues.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/AutoScaler/AutoScalerInvestigateIssues.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,160 +0,0 @@ -# Autoscaler - Debugging - -The autoscaler is running on all Helix subscriptions and HelixStaging .
-All the logs, traces, and exceptions of the autoscaler live on Application Insights. - -If you want to find any error or log about the running service, for production navigate to [dotnet-eng](https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/68672ab8-de0c-40f1-8d1b-ffb20bd62c0f/resourceGroups/dotnet-eng-cluster/providers/microsoft.insights/components/dotnet-eng/logs) and for staging navigate to [dotnet-eng-int](https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/cab65fc3-d077-467d-931f-3932eabf36d3/resourceGroups/dotnet-eng-int-cluster/providers/Microsoft.Insights/components/dotnet-eng-int/logs). You can use this [dotnet-eng/dotnet-eng-int guide](#logs-in-dotnet-engdotnet-eng-int) to navigate the information. - -If you want to see metrics or more data that is sent by the autoscaler for production navigate to [helix-autoscale-prod](https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/68672ab8-de0c-40f1-8d1b-ffb20bd62c0f/resourceGroups/helix-autoscale-prod/providers/microsoft.insights/components/helix-autoscale-prod/logs) and for staging navigate to [helix-autoscale-int](https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/cab65fc3-d077-467d-931f-3932eabf36d3/resourceGroups/auto-scale-int/providers/microsoft.insights/components/helix-autoscale-int/logs) for staging. You can use this [helix-autoscale-prod/helix-autoscale-int guide](#data-in-helix-autoscale-inthelix-autoscale-prod) to know which information you can find in there. - -## **Alerts** -The main alerts related to the autoscaler are:
- -* [Helix AutoScaler Service Stopped Running](#Helix-AutoScaler-Service-Stopped-Running)
-* [Cores consumption](#Cores-consumption) -* [Autoscale: Minutes to scale-up from zero machine](#autoscale-minutes-to-scale-up-from-zero-machine) - - -### **Helix AutoScaler Service Stopped Running** -[Helix AutoScaler Service Stopped Running Grafana](https://dotnet-eng-grafana.westus2.cloudapp.azure.com/d/arcadeAvailability/service-availability?orgId=1&refresh=30s&from=1606937931219&to=1606959531219&panelId=60&fullscreen)
-Example: https://github.com/dotnet/core-eng/issues/11478 - -Step by step: - -1. [Restart autoscaler node](#How-to-restart-the-autoscaler) as soon as you can - A lot of issues come after the autoscaler stops running such as the alert [Autoscale: Minutes to scale-up from zero machine](#Autoscale:-Minutes-to-scale-up-from-zero-machine). -2. Find when the autoscaler stopped running (hour) -3. See if there is any exception that was thrown on that period of time (on [dotnet-eng/dotnet-eng-int](#logs-in-dotnet-engdotnet-eng-int)). This will help you to know what happened. -4. If there is not an exception, you can see the traces and analyze the last trace that was recorded to have an idea in which step the autoscaler stopped running. -5. At this point the auto scaler should be running and you should be able to find the issue and fix it. - - -### **Cores consumption** -[Cores consumption Grafana](https://dotnet-eng-grafana.westus2.cloudapp.azure.com/d/quota/azure-quota-limits?orgId=1&refresh=30s&from=1606937685479&to=1606959285479&var-Resource=cores&var-Resource=standardDv3Family&panelId=30&fullscreen)
-Example: https://github.com/dotnet/core-eng/issues/11542 - -The autoscaler is in charge of enforcing the core consumption while trying to make all the queues meet the SLA, when this alert gets trigger, we should answer the following questions: - -1. Are we over-scaling? This means we have more machines than we need. Grafana [queue monitor](https://dotnet-eng-grafana.westus2.cloudapp.azure.com/d/queues/queue-monitor?orgId=1) could be an awesome way to review this. -2. Are we having problems scaling down? We should check if there are a lot of machines offline and they are not being deleted. (You can look at the [heartbeats table](#Heartbeats-table)) -3. Are a lot of machines not working and not getting clean? (The autoscaler always try to have machines heart beating so if there are a lot of machines dying the autoscaler is going to try to replace them which can lead to something similar of over scaling, for this you should check the scale sets) - -### **Autoscale: Minutes to scale-up from zero machine** -[Autoscale: Minutes to scale-up from zero machine Grafana](https://dotnet-eng-grafana.westus2.cloudapp.azure.com/d/queues/queue-monitor?orgId=1&from=1606937898053&to=1606959498053&var-QueueName=buildpool.windows.10.amd64.open&var-QueueName=buildpool.windows.10.amd64.vs2017&var-QueueName=buildpool.windows.10.amd64.vs2017.open&var-QueueName=windows.10.amd64.open&var-UntrackedQueues=%22osx%22,%20%22perf%22,%20%22arm%22,%20%22arcade%22,%20%22xaml%22,%20%22appcompat%22&panelId=99&fullscreen)
- -This alert can get triggered by a bunch of reasons but in some cases, it can be because of the autoscaler. - -There are two scenarios in which the alert normally gets triggered: -* We are having problems scaling up. -* The scale set has machines, but the machines are not heartbeating. - -#### *Scenario: Scaling up problems* -The scale up problems can come for the autoscaler or the scale set. - -1. Check if the alert 'Helix AutoScaler Service Stopped Running' is not active, if this alert is active most likely this is the reason, and you should focus on getting the autoscaler running. -2. Review the scale set and see if there are machines being created, if this is the case you know that soon there are going to be machines. Even in this case I suggest you take a quick look to see if the problem is not related to [machines not heartbeating](#scenario-the-scale-set-has-machines-but-the-machines-are-not-heartbeating). -3. If there are no machines getting created - * Check the `scaling up` traces for the queue in which the alert got trigger on [dotnet-eng/dotnet-eng-int](#logs-in-dotnet-engdotnet-eng-int). If there are not logs you should check the [machines not heartbeating scenario](#scenario-the-scale-set-has-machines-but-the-machines-are-not-heartbeating). - * Pick the more recent scaling up trace and go to the scale set and see if the scale up instruction was received and which is the status (Started, Accepted or Succeeded) to know if it is a delay on Azure. - -#### *Scenario: The scale set has machines, but the machines are not heartbeating* -The autoscaler is designed to replace the machines that are not heartbeating but this can be triggered if: -* We are out of cores. -* All the machines are offline, and we are not deleting the machines. -* The machines are not starting. -* We are taking too long to replace the machines. - -1. Review the scale set and the heartbeats table: - * If all the machines are offline, we are having problems scaling down. Start for checking if the autoscaler puts those machines offline by reviewing the "OfflineReason", the autoscaler use "Scaling Down" as OfflineReason. - * If a machine is on the scale set but is not appearing on the heartbeat table review if the machine ever appears (This information is available on HeartbeatExport table on Kusto) if it appears check the last status. Tha information can give you an idea if it has never started up or if it dies while doing a job. -2. The system is designed to always reserve cores for the queues so all the queues should be able to have at least one machine so if this happens major changes need to be made to the rebalanced cores logic. - -## **Logs in dotnet-eng/dotnet-eng-int** - -There are two tables that have information about the autoscaler `traces` and `exceptions`. - -The autoscaler is not the only service that logs data on that App Insights, so you need to filter the information, a straightforward way to filter this is using the cloud_RoleName, the identifier for the autoscaler is 'fabric:/CustomAutoScale', and inside that group there are subgroups: - -* fabric:/CustomAutoScale/ProcessAutoScaleService - This is the one in charge of managing the actors. -* fabric:/CustomAutoScale/AutoScaleActorService - This is the actor itself. -* fabric:/CustomAutoScale/ProcessTelemetryService - This reports information about the telemetry that we are sending, in most cases you can exclude this one, unless you are investigating something telemetry specific. - -When looking for an error starting with the exceptions can give information faster, in most cases, you are going to need information about ProcessAutoScaleService and AutoScaleActorService having a query like the following one: - - exceptions - | where cloud_RoleName == "fabric:/Helix/ProcessAutoScaleService" - or cloud_RoleName == "fabric:/Helix/AutoScaleActorService" - | sort by timestamp desc - -Remember to always use `Time range` to limit the information as much as you can. - -If you need more information you can always use the traces to get more data. - -If the problem is specific to a queue you should start by filtering the message with the queue, this could be done on the message or as part of a customDimensions: - - | where message contains "windows.10.amd64.open.rt" - - or - - | where customDimensions.queue == "windows.10.amd64.open.rt" - or customDimensions.queueName == "windows.10.amd64.open.rt" - - -If it is related to a specific problem, there are a couple of keywords that we normally use to filter the data, and this can be filtered as part of the message: - -* Scaling (or scaling up / scaling down) -* Deleting -* Machines online -* MaxCapacity (this is related to the cores assigned for queue) - -For example: - - traces - | where cloud_RoleName == "fabric:/CustomAutoScale/ProcessAutoScaleService" - or cloud_RoleName == "fabric:/CustomAutoScale/AutoScaleActorService" - | where customDimensions.queue == "windows.10.amd64.open.rt" - | where message contains "scaling down" - | sort by timestamp desc - - -## **Data in helix-autoscale-int/helix-autoscale-prod** -If you want more information about how the autoscaler is behaving, you can look at helix-autoscale-int/helix-autoscale-prod Application Insight in which information is recorded on `custom events ` table with the following names: - -* AdjustCapacity: Every 30 seconds reports all the variables that are important for the autoscaler to decide how many machines need, this are active messages, current capacity, desired capacity, machine creation time, max capacity, work items per machine and SLA. -* AutoScaleReport: Every 30 seconds reports the current capacity (machines heartbeating) and queue depth. -* QueueReport: Every minute reports queue depth and state of the heartbeating machines (initializing, offline, online, busy). This data can be analyzed on Grafana on [Queue Monitor Production](https://dotnet-eng-grafana.westus2.cloudapp.azure.com/d/queues/queue-monitor) or [Queue Monitor Staging](https://dotnet-eng-grafana-staging.westus2.cloudapp.azure.com/d/queues/queue-monitor). -* QueueCapacityChanged: When the capacity of a queue changes send data about current capacity, previous capacity, and future capacity. - -## **Additional Info** - -### How to restart the autoscaler -1. For production navigate to [Service Fabric Explorer dotnet-eng](https://dotnet-eng.westus2.cloudapp.azure.com:19080/Explorer/index.html#/) and for staging navigate to [Service Fabric Explorer dotnet-eng-int](https://dotnet-eng-int.westus2.cloudapp.azure.com:19080/Explorer/index.html#/).
(If you have problems accessing this site, remember that you need a [certificate](#get-the-certificate-to-access-autoscaler-cluster)) -2. Find in which node is running ProcessAutoScaleService: - * Open the following tabs: CustomAutoScaleType -> fabric:/CustomAutoScale -> Service fabric:/CustomAutoScale/ProcessAutoScaleService. - * Open one more tab and you are going to be able to see the name of the node, is going to look like this: _Primary_3. -2. Open the Nodes tab and find the node name that you got on the previous step. -3. Go to Actions button and click on Restart. - -### Get the certificate to access autoscaler cluster -1. Navigate to [HelixProdKV | Certificates](https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/68672ab8-de0c-40f1-8d1b-ffb20bd62c0f/resourceGroups/helixinfrarg/providers/Microsoft.KeyVault/vaults/HelixProdKV/certificates) / [HelixStagingKV | Certificates](https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/cab65fc3-d077-467d-931f-3932eabf36d3/resourceGroups/helixstagingkvrg/providers/Microsoft.KeyVault/vaults/HelixStagingKV/certificates) -3. Select `dotnet-eng-client-westus2-cloudapp-azure-com` certificate. -4. Open the current version. -5. Download in PFX/PEM format: - - Leave password empty - - Certificate Store select Place all certificates in the following store > Browse > Personal - -### Heartbeats table -To access the heartbeats table using Azure Portal, follow these steps: -1. Navigate to [helixscripts2](https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/68672ab8-de0c-40f1-8d1b-ffb20bd62c0f/resourceGroups/helixinfrarg/providers/Microsoft.Storage/storageAccounts/helixscripts2/storageexplorer)/[helixstagescripts2](https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/cab65fc3-d077-467d-931f-3932eabf36d3/resourceGroups/helixstaginginfrarg/providers/Microsoft.Storage/storageAccounts/helixstagescripts2/storageexplorer) -2. Open the Tables tab. -3. Click on heartbeats. - -To access the heartbeats table using Microsoft Azure Storage Explorer, follow these steps: -1. Open Microsoft Azure Storage Explorer. -2. Navigate to Helix/HelixStaging subscription. -3. Find the storage account helixscripts2 or helixstagescripts2. -4. Open the Tables tab. -5. Click on heartbeats. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CAutoScaler%5CAutoScalerInvestigateIssues.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CAutoScaler%5CAutoScalerInvestigateIssues.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CAutoScaler%5CAutoScalerInvestigateIssues.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/build-failure-buckets.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/build-failure-buckets.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/build-failure-buckets.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/build-failure-buckets.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ - - -### Misc references: -- Here’s the project you need to add a reference to in order to get a stream to an azure blob: https://mseng.visualstudio.com/Tools/Engineering%20Infrastructure/_git/CoreFX%20Engineering%20Infrastructure?path=%2Fsrc%2Fcommon&version=GBmaster&_a=contents -- Here is the class that has the helpers around blobs (https://mseng.visualstudio.com/Tools/Engineering%20Infrastructure/_git/CoreFX%20Engineering%20Infrastructure?path=%2Fsrc%2Fcommon%2Fcommon%2Fazure%2FAzureBlobStorage.cs&version=GBmaster&_a=contents); right now there’s a constructor that requires a storage account even though the function you need (GetExternalBlobReadStreamFromSasTokenUrlAsync) doesn’t utilize that account. We can add a parameter-less constructor to that class if you need  - -- regex for buckets from Jenkins: https://ci.dot.net/failure-cause-management/ -- Plug in itself: https://github.com/jenkinsci/build-failure-analyzer-plugin -- XML hurl w/ regex: https://ci.dot.net/userContent/build-failure-analyzer.xml - -- Aho-Corasick Alg for searching large files: https://github.com/pdonald/aho-corasick (https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm) - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5Cbuild-failure-buckets.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5Cbuild-failure-buckets.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5Cbuild-failure-buckets.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/buildingvertical.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/buildingvertical.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/buildingvertical.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/buildingvertical.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,124 +0,0 @@ -# Building a Vertical Implementation Details # - -**Definitions** - -*VerticalTargetGroup* - -`VerticalTargetGroup` - We need a property to define the vertical target group, but we don't want to set "TargetGroup" explicitly or we won't be able to build the "" TargetGroups for projects. - If `VerticalTargetGroup != ""`, we import buildvertical.targets which will contain our additional targets. - -*SupportedGroups* - -For each ref project and src project, we define `SupportedGroups`. `SupportedGroups` is a tuple for the supported `TargetGroups` and `OSGroups`. - - -ie - -ref\System.Runtime.csproj -```MSBuild - - - netstandard1.7_Windows_NT; - netstandard1.7_OSX; - netstandard1.7_Linux; - netcoreapp1.1_Windows_NT; - netcoreapp1.1_OSX; - netcoreapp1.1_Linux - - -``` - -*Contract Layer* - -We have a contract layer (msbuild task). - -Inputs: - - SupportedGroups - VerticalTargetGroup - OSGroup -Output: - - VerticalTargets (ItemTask) - metadata: TargetGroup - OSGroup - -Given the supported target and OS groups, and the desired vertical target and OS groups, return the closest supported group or empty metadata items. -How should we handle determining the target / os groups, fallback groups, etc...? The simplest solution is to use the NuGet api's for targets. We can use platforms\runtime.json for os groups, or try to use the already existent os group filtering instead of adding it to the contract layer. - -Options: - -1. Use NuGet API's - -2. Make use of inormation we already have and develop our own resolution algorithm. - -The current plan is to use the NuGet API's. We know that there is an intrinsic problem with the NuGet API's, in that we (CoreFx) define the targets (tfm's), but NuGet contains the data / logic, so anytime we want to create a new tfm, we have to go make an update to NuGet. This is an existent problem. For now, it is much simpler to utilize NuGet instead of deriving a second solution. When we break free of the NuGet dependency and wholly define our tfm graph, then we should utilize that solution for this work. - -**Building a vertical implementation steps** - -1 - Include all projects, we don't need to build the .builds files for each library, because we only want to build each project at most once for a given vertical. - -```MSBuild - - - - -``` - -2 - Iterate all projects through the contract layer, removing (and logging) any projects which return null metadata (not supported). - -3 - Build `OutputPath` is set to drop all binaries into a single folder - -Current standard `OutputPath` - -```MSBuild -$(BaseOutputPath)$(OSPlatformConfig)/$(MSBuildProjectName)/$(TargetOutputRelPath)$(OutputPathSubfolder) -``` -Example: E:\gh\chcosta\corefx\bin/AnyOS.AnyCPU.Debug/System.Buffers/netcoreapp1.1/ - -Proposed vertical `OutputPath` - -```MSBuild -$(BinDir)/$(VerticalTargetGroup)/$(OSPlatformConfig) -``` -Example: E:\gh\chcosta\corefx\bin/netcoreapp1.7/AnyOS.AnyCPU.Debug - -Traditionally, the output path contains the `TargetGroup` as a part of the path. The flat structure means we don't have to play games with the `TargetPath` to figure out when, for example, "System.Buffers" ("netstandard1.1") is trying to find the "System.Runtime" reference ("netstandard1.7"), that there is no path for "System.Runtime.dll" containing the "netstandard1.1" target group. - -4 - Build all reference assemblies. The reference assembly projects, which were not trimmed in step 2, are all built. TBD, should we again use the contract layer during the build to determine the targets for the project, or should we capture that as metadata for the project in step 2? - -5 - Build all src assemblies into the "OutputPath". The src assembly projects, which were not trimmed in step 2. are all built. - -6 - build packages, TBD - -**Building a library** - -In addition to the ability to build an entire vertical, we require the ability to build a single library. This, single library build should utilize context to determine TargetGroup and OSGroup. ie, If a vertical build completes, and you want to build an individual library, it should use the group values from the vertical build unless you specify otherwise. If you specify otherwise, then those settings become the new settings. If no context is available, then the library should be built with a set of commond default values. - -When building an individual library, or project, its P2P references must be queried to determine supported configurations for building that refernce and then the best configuration must be chosen. - -**Additional issues** - -- building specific folders (filter by partition)? - -- building / running tests for a vertical - - - building tests against packages - -- Official builds? - -- CI testing? - -- Validation - - - Is it an error condition if any library does not contribute to the latest standard vertical? - - - Is it an error condition if a library does not contribute to any OS group? probably - - - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5Cbuildingvertical.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5Cbuildingvertical.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5Cbuildingvertical.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/cmake-design.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/cmake-design.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/cmake-design.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/cmake-design.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,60 +0,0 @@ -# Design Contract for Searching and Acquiring Build Prerequisites - -This document presents the design contract for searching and acquiring build prerequisites. Scope is limited to developer workflow in [CoreFx](https://github.com/dotnet/corefx.git) repository. - -A CoreFx developer uses `build.cmd` or `build.sh` to build the repository. Build requires certain tools, for example, CMake. Build should have a specified set of locations to search a tool, and if the tool is not found in any of those locations then, acquire the tool from a specified URL. This document describes the contract between the build and scripts that search and acquire a tool. - -####Tool Manifest - -Details of each tool required for the build will be in a manifest file `.toolversions` located in the root of the repository. Each tool should have the following details: - - 1. Name of the tool - 2. Declared version - 3. Search paths where the tool is likely to be found - 4. Acquire paths from where the tool can be obtained - -An example of `.toolversions` is shown below: - ----------- -![toolversions.](./assets/toolversions.png?raw=true) - ----------- - -####Probing Mechanism - -Build will use a probing mechanism to get the required tool. For any tool, probing involves the following three tasks in sequence: - - 1. Search the tool requested by the build. Searches the tool in locations specified in `.toolversions` If tool is found then, return the tool path to the build. - 2. If search fails to find the tool then, acquire the tool from the location specified in `.toolversions` - 3. If search and acquire fail then, return an error message to build - -####Search - -ISearchTool interface provides virtual and abstracts methods that accomplish searching of a tool. Default implementation would search the tool in environment path and a location within the repository specified in`.toolversions`. A tool can override the base, and have its own implementation of search. -Each tool should implement abstract methods. - -####Acquire - -IAcquireTool interface provides virtual and abstracts methods that accomplish the acquisition of a tool. Default implementation would download the tool from the URL specified in `.toolversions`, and extract the tool to a location within the repository specified in `.toolversions`. A tool can override the base, and have it own implementation of acquisition. -Each tool should implement abstract methods. - -####Helpers - -Helpers are a set of utility functions. For example, a function that can parse `.toolversions` and get the declared version of a tool. Probe, search and acquire scripts will use these functions. - -Probe, search and acquire scripts will be in `tools-local` folder in root folder of CoreFx repository. -A short description of each folder under `tools-local` is provided in the table below. - -Folder | Description ------- | ----------- -unix | Shell scripts that provide default implementation of ISearchTool and IAcquireTool, probe-tool, and helpers -unix/cmake | Shell scripts that override the default implementation, and implement abstract methods for CMake. -unix/clang | Shell scripts that override the default implementation, and implement abstract methods for CMake. - -Similar folder structure will be available for Windows PowerShell scripts. `Tools/downloads/` in CoreFx repository root will be the default location for downloaded tools. - -Official builds will override the default locations where the tool is searched and acquired. These override locations for official builds are specified in a copy (not available in open) of `.toolversions` file. If a path to the override is specified as a command line argument to `build.cmd` or `build.sh` then, config values from `.toolversions` file located in that specified path are used in search and acquire scripts. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5Ccmake-design.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5Ccmake-design.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5Ccmake-design.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/cmake-scenarios.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/cmake-scenarios.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/cmake-scenarios.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/cmake-scenarios.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,177 +0,0 @@ -This document presents the scenarios for CMake in .NET Core. - -# Summary -[CMake](https://cmake.org/overview/) is a prerequisite for building .NET Core repositories such as CoreFx and CoreCLR. When a developer attempts to build, a repository using the build script, the script probes for CMake on the machine, and if CMake is not found then, the script terminates and the build fails. There is no guidance for developer on which version of CMake to install. Not stating a version of CMake for a repository can lead to further challenges for example, when a servicing a release. - -Thus, there is need to improve CMake usage and acquisition experience. Hence the purpose of this document is to define the scenarios for CMake in .NET Core, and lay a foundation for designing the improved experience. - -# CMake Scenarios -Each subheading below is a name of a scenario. Under each subheading there will be a short description of the scenario and a narrative. The narrative includes an actor, flow of events, and the resulting outcomes. An outcome consists of one or more pairs of the current and desired experiences. - - * [Build a .NET Core repository on a clean machine](#build-a-net-core-repository-on-a-clean-machine) - * [Build a .NET Core repository on an existing development machine](#build-a-net-core-repository-on-an-existing-development-machine) - * [Setup an official build for a .NET Core repository](#setup-an-official-build-for-a-net-core-repository) - * [Service a .NET Core release](#service-a-net-core-release) - * [Revise the CMake version of a .NET Core repository](#revise-the-cmake-version-of-a-net-core-repository) - * [A new version of CMake can be tested against a .NET Core repository](#a-new-version-of-cmake-can-be-tested-against-a-net-core-repository) - * [Guidance for setting up official builds for a .NET Core repository is available](#guidance-for-setting-up-official-builds-for-a-net-core-repository-is-available) - -## Build a .NET Core repository on a clean machine -A .NET Core user (Microsoft employee or someone in the open) clones a .NET Core repository and attempts to build. - -### Narrative -I am an IT Manager from Contoso who was inspired by .NET Core demos at [Connect(); 2016](https://msdn.microsoft.com/en-us/magazine/connect16mag.aspx), and I would like to contribute to .NET Core. - -Flow of events: - 1. I setup a clean Windows 10 VM through my Azure subscription. - 2. I cloned CoreFx repository from [dotnet/corefx](https://github.com/dotnet/corefx.git) - 3. I attempted to build the repository using the build command (build.cmd) - -### Outcome #1 Build fails -**Current**: Build fails saying CMake, which is a prerequisite, is missing on the VM. Though the error message provides an URL from where CMake can be downloaded, it does not list a specific version or a range of supported versions. I am not certain on what version to download. - -**Desired**: -Build probes for CMake in .NET Core sandbox tools folder in addition to the current probing locations. If CMake is not found, then the build attempts to acquire the declared version of CMake from the ***tools*** cache. If acquisition fails, then the build presents an error message that informs the user the specific version of CMake to download, and the suggested source to download it from. I have two options from here: - - - I download the specific version and perform a default install. *(TBD: Should the user restart Command or Terminal window?).* - - Perform a gesture described in the error message to acquire CMake. I perform the gesture so that a tool downloads and extracts the declared version of CMake to a .NET Core sandbox tools folder. - -Either of the above options allow me to run build command successfully. - -## Build a .NET Core repository on an existing development machine -A .NET Core user would like to clone a .NET Core repository to his/her existing development machine, and build that repository. - -### Narrative -I am an IT Manager from Contoso who was inspired by .NET Core demos at [Connect(); 2016](https://msdn.microsoft.com/en-us/magazine/connect16mag.aspx), and I would like to contribute to .NET Core. - -Flow of events: - 1. On my existing development machine, I cloned CoreFx repository from - [dotnet/corefx](https://github.com/dotnet/corefx.git) - 2. I attempted to build the repository using the build command (build.cmd) - -### Outcome #1: Build succeeds -**Current**: I have no indication about the CMake version used. - -**Desired**: I can refer to a build artifact to find the version of CMake used in the build. - -Note: Since an existing development machine is being used, the machine could have the declared version of CMake. - -### Outcome #2: Build fails -**Current**: - - - Build fails saying CMake, which is a prerequisite is not available on the machine. I try to download CMake based on the error message. I am not certain on what version to download. - - Build fails in strange ways due to a version of CMake present on the machine, and thus making it difficult to trace back. - -**Desired**: - - 1. On a clean machine, the desired experience should be same as earlier scenario [Build a .NET Core repository on a clean machine](#build-a-net-core-repository-on-a-clean-machine) - 2. On an existing machine that has a version of CMake, which is different than the declared version, there are two outcomes: - 1. Default outcome is build consumes the available CMake version. - 2. I can ensure the build consumes the declared version of CMake. This means if the build detects that the version of CMake available is not the declared version, then the build attempts to acquire the declared version. This acquisition experience should be same as in the earlier scenario [Build a .NET Core repository on a clean machine](#build-a-net-core-repository-on-a-clean-machine). - -Either of the above options allow me to run build command successfully. - -## Setup an official build for a .NET Core repository -A .NET Core repository owner would like to setup a reliable, repeatable and trustable process of producing official builds. - -### Narrative -I am the owner of [.NET CoreFx](https://github.com/dotnet/corefx) repository. I would like to setup a process that will produce reliable, repeatable and trustable builds for this repository. - -Flow of events: - - 1. Created a new VSTS build definition that runs the build command of the repository. - 2. Ensured the builds succeed - -### Outcome #1: Official builds consume the declared version of CMake. - -**Current**: .NET Core repository is built using a version of CMake installed while setting up the VM. Version of CMake is not logged in build artifact. - -**Desired**: - 1. I can find the declared version of CMake for CoreFx or any .NET Core repository within the repository itself. - 2. I can setup official build of a .NET Core repository such that the build acquires the declared version of CMake from OSS Tools repository, and places it in a sandbox location. - 3. I can ensure that the official build utilizes CMake tool from the sandbox folder. - -Note: OSS Tool repository will download CMake source code, security audit the source code, build and then host. Thus, minimizing any security risks that might arise when CMake is consumed directly from internet. - -### Outcome #2: Official builds setup and maintenance is reliable and costs lowered -**Current**: Build agents are tied to a specific version of CMake. - -**Desired**: Build agents are more contained and not dependent on a particular build agent for CMake installations. Thus, the same agent can build multiple repositories and branches. - -## Service a .NET Core release -A .NET Core team member who would like to service a release, and needs the release configuration to rebuild. - -### Narrative -I am a .NET Core team member who is assigned to service a [CoreFx](https://github.com/dotnet/corefx) release to address an issue reported in the product. - -Flow of events: - 1. I checked out the release branch on my local developer machine. Understood the root cause of the reported issue. - 2. I created a service branch i.e., fork from release branch, and have a fix ready. - 3. I followed the developer guidelines available for that branch to perform a build. - -### Outcome: Service branch builds consume the declared version of CMake -**Current**: Since a declared version of CMake is not available within the repository, I will build the service branch using the latest version of CMake available. This latest version of CMake might introduce new product behaviors that I will have to resolve. Thus, lack of declared version introduces uncertainty and additional costs in servicing a branch. - -**Desired**: - 1. I can run the build such that if the required toolset does not match then, the build acquires the required toolset. - 2. I can find out the CMake version used to build the release branch. Declared version is available within the repository itself. - 3. I can acquire and run build with tools in a sandbox folder. - -## Revise the CMake version of a .NET Core repository -A .NET Core contributor can update the declared CMake version for a given .NET Core repository. - -### Narrative -I am a .NET Core contributor working on new features in CoreFx. These features require the latest version of CMake. - -Flow of events: - 1. I verified the compatibility of existing features with the new version of CMake. - 2. I would like to update the product to be built using this new version of CMake. - -### Outcome: All build scenarios are aware of the new version of CMake -**Current**: Though as a .NET Core contributor I can build a local repository using different versions of CMake, doing the same with official builds involves cost such as updating build definition, notifying repository owners, and finally inform the open community of users about the new version of CMake. - -**Desired**: As a .NET Core contributor I can modify declared CMake version, and submit this change as a pull request (PR). The change to declared CMake version is reflected in all build scenarios. This means, the official builds will pick up the new version from OSS Tools repository, and other scenarios reflect the change to the declared version of CMake. - -Note: A requirement for this scenario is that the new version of CMake should be available on OSS Tool repository. - -## A new version of CMake can be tested against a .NET Core repository -A .NET Core team member can try a new version of CMake to build a .NET Core repository. - -### Narrative -As a team member of .NET Core or Engineering Services, I would like to check the applicability of a new version of CMake in a .NET Core repository. For instance, verify if a new version of CMake is compatible with a .NET Core repository, and no unexpected regressions in the product are introduced. - -Flow of events: - 1. I built the local repository using the new version of CMake. Ensure build succeeds. - 2. I would like to test official builds produced with this new version of CMake. - -### Outcome: A test run of official build consuming the new CMake package can be performed -**Current**: Updating an official build to test a new version of CMake involves the following steps - - 1. Creating a new agent pool - 2. Adding VMs to the pool where each VM has the new version of CMake installed - 3. Setting up new build definitions that point to this pool - -**Desired**: - 1. I'm able to build a .NET Core repository using a CMake version, which is not in the declared version. - 2. I can perform a test run of official build and ensure that the CMake package is consumed, and build succeeded. - -## Guidance for setting up official builds for a .NET Core repository is available -A .NET Core repository owner refers to documentation that describes the procedure to setup up official builds. - -### Narrative -I'm team member on Red Hat. I would like to setup official builds of our CoreFx repository using the same CMake version used in .NET Core official builds. - -Flow of events: - 1. I forked CoreFx repository from [dotnet/corefx](https://github.com/dotnet/corefx.git) - 2. I followed the developer guidelines to setup official builds - -### Outcome: .NET Core users refer to documentation about setting up official builds -**Current**: In my attempt to setup official builds, I will use the latest version of CMake in official builds since CoreFx documentation does not provide any guidance on declared CMake version. Native binaries from Red Hat build is now significantly different from those produced in .NET Core product team's official builds. This difference is due to the different versions of CMake used in the respective builds. - -**Desired**: - 1. Declared version of the .NET Core repository is available within the repository itself. I can find it, and enforce it with my toolset story - 2. I can specify a version of CMake that should be consumed in the build command of my .NET Core repository. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5Ccmake-scenarios.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5Ccmake-scenarios.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5Ccmake-scenarios.md) - diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Core Reduction/Automated_MaxScale_Updates.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Core Reduction/Automated_MaxScale_Updates.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Core Reduction/Automated_MaxScale_Updates.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Core Reduction/Automated_MaxScale_Updates.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -## **Proposal for automating max scale updates** ## - -### **Steps needed to calculate max scale for queues:** ### - -1. Determine the usage for the queue for the past 60 days by querying the Kusto database and getting the workitems that were queued, process and completed during this time frame. -2. Determine the average waittime and 95th percentile waittime for the queue based on #1 -3. Determine max machines / cores used to process the workitems from #1. - -The above steps are done by using the [Scaler Simulator](#Scaler-Simulator) tool. - -### **Scaler Simulator:** ### - -[Scaler Simulator](https://dev.azure.com/dnceng/internal/_git/dotnet-helix-machines?path=%2Ftools%2FScalerSimulator&version=GBScalerSimulator&_a=contents) is a tool that can currently do the following: - -- Query Kusto to get workitems for a specific queue for a specified time frame. -- Calculate the average and 95th percentile wait time for the queue based on data from Kusto. -- Determine the max machines that were needed to process the workitems for that specific queue. -- Given a max set of machines for a queue, the simulator can simulate the processing of workitems (based on the Kusto query above) and calculate the average and 95th percentile waititme. -- Given a SLA for wait time, the simulator can also reverse-engineer and determine how many machines would be needed for the queue to process the workitems(based on the Kusto query above) - -### **Automation Proposal Stages:** ### - -**Stage 1:** - -* Extending Scaler Simulator to do the following based on the assumption that the baseline of core/machine distribution is already arrived at in the machine config yamls: - - * Inputs: - * Total machines / cores that can be used - * 95th percentile SLA for wait time for Build Queues - * 95th percentile SLA for wait time for Test Queues - - * Process/simulate each queue in the list of queues that are active (this list currently is hardcoded in a csv but can be extended to pick non-deadlettered queues from info.json directly) to determine what the max machines that are needed to process the load in the queue to be able to hit the SLA given. - - * Rebalance the max machines allocated based on the past usage by maintaining a running surplus list during the course of a simulator run.A successful run spits out the updated max scale list for the queues processed. - - * i. If the simulation determines that the max machines needed for a specific queue is less than the max scale already set, then update the max scale and add the remaining to the surplus. - * ii. Maintain a list of queues that need more machines than the current max scale value. If the simulation determines that the max machines needed are more than the max scale already set, then update the list with the queues and how much more is needed. - * iii. Once the simulation finishes processing all queues and a final surplus machines count is available, allocate / distribute the surplus among the list of queues identified in #ii. TBD – Actual implementation on the mechanics behind the distribution process. - * iv. While doing #iii, if the simulator exhausts all items in the surplus list and there are still queues that require more machines than the max scale that is already set, then fail the simulator letting the user know “This operation will exceed the max total cores allowed, please readjust the total cores or the wait time SLA” - * v. During the process, if any queue has no past usage then mark the queue to be deadlettered unless identified as a new queue. TBD - process to identify a new queue. - -**Stage 2:** - -* The extended simulator opens a PR with the determined changes to the max scale to dotnet-helix-machines master branch which then will be reviewed and merged by a human. The change will then follow the normal rollout process to make it to Production. - -**Stage 3:** - -* The extended Simulator can be run manually by providing the inputs on as-need-basis. If we determine that this is needed to be run on a regular cadence, then the simulator can be hooked up to a separate pipeline and schedule a run based on the need with the variables of the pipeline as inputs. - -**Stage 4:** - -* Instead of using a simulator to determine the max scale, rebalance the max scale on the fly based on the usage by the custom auto-scaler and is able to take in the input of what the total machines allowed are to consider while rebalancing. TBD - Feasibility of implementing this into auto-scaler and ability to test this thoroughly. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CCore%20Reduction%5CAutomated_MaxScale_Updates.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CCore%20Reduction%5CAutomated_MaxScale_Updates.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CCore%20Reduction%5CAutomated_MaxScale_Updates.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/custom-auto-scaling.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/custom-auto-scaling.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/custom-auto-scaling.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/custom-auto-scaling.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,48 +0,0 @@ -# Custom Auto-Scaler v2 - -The goal of this project is to build a reliable and cost-efficient scaling system to manage Azure Scale sets for Helix VMs. - -## Telemetry processor - -Telemetry processor collects the following data: -- From service bus, the number of workitems in the queue -- From the heartbeats table, the number of machines that have had a heartbeat in the last minute -It is implemented as a service in service fabric, runs every 30 seconds and sends the collected data to AppInsights. - -![](./assets/Telemetry.png) - -This replaces ProcessTelemetry Azure function in Custom Auto-Scaler v1 - -## Alerting - -The system uses Grafana to create alerts when Helix queues aren’t scaling, it will create a GitHub issue when there has been work in a queue waiting for more than 30 minutes and no machines have showed up in the heartbeats table. Grafana doesn’t query the heartbeats table directly, instead it reads the data from AppInsights to tell how long the work items have been waiting for a machine. - -![](./assets/GrafanaAlert.png) - -At first, the Auto Scaler team monitors these alerts but after the stabilization phase FR will investigate these alerts. - -## Scale in - -Custom Auto-Scale v2 scales to any capacity the service requires. -For scaling in, the service uses Helix API to take machines offline and then deletes the machine from the corresponding scale set. It will delete the oldest idle machine in the queue trying to keep the machines as fresh as possible, it uses the information in the heartbeats table to get the creation date of each machine. - -![](./assets/MachinesOffline.png) - -## Telemetry - -The service sends to AppInsights the following telemetry -- Time it took for a queue to scale to the desirable capacity -- State of the queue and the desired capacity for this state - -Core-eng uses this data to make adjustment in the scaling rules - -## Multiple subscriptions - -Every team, that requires it, has its own queues for their work reducing the noise caused by using shared queries, giving information of how much money is being spent running their jobs and allowing to modify their scaling rules as needed. - -![](./assets/MultipleSubs.png) - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5Ccustom-auto-scaling.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5Ccustom-auto-scaling.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5Ccustom-auto-scaling.md) - diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dependency Flow/improved-dependency-flow.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dependency Flow/improved-dependency-flow.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dependency Flow/improved-dependency-flow.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dependency Flow/improved-dependency-flow.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,154 +0,0 @@ -# Improved Dependency Flow for .NET 5 - -## Goals - -* Eliminate the need for opening Pull Requests for inter-repository dependency - updates in channels where breaking changes are the exception, in order to - reduce the total time it takes to achieve a full product build by skipping PR - validation builds. - -* Keep the existing dependency flow PR functionality in place for channels and - repos that won't benefit from the new flow. - -## Non-Goals - -* We are not looking to optimize dependency flow for channels and repos that are - in active development. Constant commits to the target branches for - subscriptions will result in the new flow not being able to automatically - merge changes, and will instead result in delays opening the dependency update - PRs. - -* We won't thoroughly validate builds that result from applying dependency - updates with the new flow. Since we will only run the official builds for the - target repositories, any testing that is performed during PRs will be skipped, - and any failures during CI builds won't have an associated PR that caused the - break. - -## Overview - -In order to reduce the number of dependency update PRs that flow across the -stack, we will attempt to apply dependency updates directly on the branches that -subscriptions are targetting. In order to accomplish this, Maestro++ will -dynamically create branches to apply dependency updates to, run an official -build for the repo, and if both the build succeeds, and a fast-forward merge to -the target branch is possible, it will directly merge the dependency updates. In -cases where there's an error in the build that takes the new dependencies, or in -cases where a fast-forward merge is not possible, we will fall back to opening a -PR just like it happens today. - -This flow will ensure that dependency updates are seamlessly flowing across the -stack without manual intervention, especially for release channels, where -breaking changes are the exception, and dependency update commits make up the -brunt of the branch's history. This means that PR validation builds are -constantly increasing the times required to flow depenencies. - -![release-branch-history-example](release-branch-history.png) - -## Repository Requirements - -We would like to keep the changes that product repositories need to perform to a -minimum. However, some configuration will need to be performed for repos that -wish to participate. - -* Since we will depend on running an official build of the repo from a branch - that is created and deleted once the dependeny flow process finishes, we will - require that there are no unwanted side effects based on the branch name that - runs the build. (For example, "Unless running the build from the master - branch, copy blobs from location A -> B, and fail in other cases) - -* Branch policies need to be configured such that the bot account that will - perform the merging of updates needs to have push permissions to any branch - where this functionality is to be enabled. - -## Subscription Changes - -* We will introduce a new option for subscriptions to opt-in into the new direct - merge behavior. This option will be provided for both existing and new - subscriptions. - -* As Maestro++ will have to be able to monitor the official builds for a - subscription's target repository, we will use the build information currently - in the BAR to attempt to pre-populate the Azure DevOps mirror for the - repository, along with the build definition that hosts its official build. In - cases where it's not possible to infer, this information will need to be - provided by the user when setting up or updating the subscription. - -## Maestro++ Service Changes - -When processing dependency updates for a subscription that has the new option -enabled, the Maestro++ service will: - - 1. Create a branch in the internal target repository (internal AzDO mirror for - GitHub repos, the base repo for Azure DevOps repos) based off the head of - the target branch for the subscription in the format: - `darc-flow--` - - 1. Apply the version updates to the `darc-flow` branch, In the case of batched - subscriptions, the service will wait some time for the various updates to - come in and apply them to the same branch. - - 1. Trigger and monitor an official build of the repository for the `darc-flow` - branch. - - 1. If the build is successful, attempt a fast-forward only merge into the - target branch. If successful: - * Trigger the [Dnceng Build Promotion - Pipeline](https://dnceng.visualstudio.com/internal/_build/results?buildId=550056&view=results) - or the [DevDiv Build Promotion - Pipeline](https://devdiv.visualstudio.com/DevDiv/_build?definitionId=12603&_a=summary) - depending on which org hosts the official build pipeline for the repo to - publish the build assets to the feeds and blob storage, and add the - build to the target channel for the subscription. This will cause - downstream dependency updates to trigger. - * Once merged into the main repository, the commit into the internal - mirror will end up triggering another official build, which will for all - intents and purposes produce identical assets as the ones produced in - step 4. This build will be tagged to indicate it's a duplicate, and will - be cancelled and deleted from the build pipeline's history. - - 1. If the fast-forward merge fails: - * Attempt to merge the version updates with a merge commit. The official - build will be triggered by this commit once it gets mirrored to the - internal repo, and this build will publish and be added to the channels - automatically via the branch's default channel. - - 1. If there's a failure in the official build, or if there are merge conflicts - when attempting to merge, the version updates will be re-applied against - the HEAD of the target branch, and the existing flow model that opens Pull - Requests in the target repo will kick in. - - 1. Clean up the `darc-flow` branch. - -**Note:** In cases where additional dependency updates targetting the same -branch need to be processed after the official build has been triggered but -before the commit has been merged into the taget branch, the new dependency -update will be queued until the steps have finished for the current set of -updates. - -## Timeline Ingestion Changes - -In order to avoid skewing the data used for the various reports, we will modify -the Telemetry application so that builds that have been tagged by Maestro++ as -duplicates are ignored and not ingested into Kusto. - -## Rollout and Validation Plan - -1. We will initially enable the functionality in the test repositories in the - [maestro-auth-test](https://github.com/maestro-auth-test) org and will only - be exercised by the Scenario tests that run on every check-in of the - arcade-services repository. -1. Enable it in the Arcade-services and Arcade-Validation repositories. These - repos won't need any only take dependency updates from Arcade at a - predictable cadence and have stable official builds. We should see the new - flow working most of the time. This will require some work to make sure that - only the build stages of the Arcade-Services pipeline run, as to not actually - deploy anything during the official build of the `darc-flow` branches. -1. Once we are comfortable with the results of the controlled testing, we can - start enabling the functionality in product repos for the .NET 5 preview - channels. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CDependency%20Flow%5Cimproved-dependency-flow.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CDependency%20Flow%5Cimproved-dependency-flow.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CDependency%20Flow%5Cimproved-dependency-flow.md) - Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dependency Flow/release-branch-history.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dependency Flow/release-branch-history.png differ diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dev WF Actionable PRs/Capabilities and Data.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dev WF Actionable PRs/Capabilities and Data.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dev WF Actionable PRs/Capabilities and Data.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dev WF Actionable PRs/Capabilities and Data.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,60 +0,0 @@ -# Capabilities and Data - -Taking as inputs the discussions had so far, the Checks Tab mock-ups Chad completed and the scoping done during our latest Gap-minding meeting I did some investigations and experiments to better understand the AzDO APIs, especially around tests. I'm happy to say it looks like it will be a good solution for augmenting data we already store in Kusto, solving problems of timeliness (e.g. Kusto ingestion delay) and scale (e.g. test results).  - -Going through the meeting notes and the Checks Tab mocks, I organized the questions this system is planning to answer and where that information will be retrieved.  - -Generally, the Checks Tab frames information in terms of - -- Build failures -- Test failures - -Each type of failure showing information about - -- If unique -- If seen previously -- If happening now or recently on target (master) branch - -## Concerning current build failure information - -Run error information determined from aggregate of - -- AzDO Get Build API -- AzDO Build Timeline API -- AzDO Get Test Run API - -Answering questions or displaying information: - -- "\ [Test] [History] [Artifacts]" -- "Exception message \" -- "Callstack \" - -## Concerning Build Retry information - -Retry information is aggregated from - -- Current build failure information -- Auto-retry driving telemetry in SQL "Known Failure" table - -Answering questions - -- "This test \ pass on retry" -- Concerning Branch Status -- "The target branch (master) \ failing" (AzDO "Get Build" API) - -## Concerning Historical build failure information - -- "This step first failed in master on \" (Kusto "Timeline*" tables) -- "This step has failed \ out of \ runs in master, most recently on \" (Kusto "Timeline*" tables) - -## Concerning test history by branch, test pass/failure rate,  - -- "There are test failures in this build for pipelines that are also failing in master" (AzDO "Query Test History" API) -- "The test \ has failed out of runs" (AzDO "Query Test History" API) -- "The test \ first failed on master at \ on \" (AzDO "Query Test History" API) -- "The test \ latest failed on master at \ on \" (AzDO "Query Test History" API) -- "The test \ was introduced on \" (AzDO "Query Test History" API) - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CDev%20WF%20Actionable%20PRs%5CCapabilities%20and%20Data.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CDev%20WF%20Actionable%20PRs%5CCapabilities%20and%20Data.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CDev%20WF%20Actionable%20PRs%5CCapabilities%20and%20Data.md) - diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dev Workflow/Failure Guessing - arcade5963.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dev Workflow/Failure Guessing - arcade5963.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dev Workflow/Failure Guessing - arcade5963.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dev Workflow/Failure Guessing - arcade5963.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,74 +0,0 @@ -# Failure Guessing for Pull Requests - -We want to implement a mechanism that will trigger when a build fails and provides information (much like the [runfo](https://github.com/jaredpar/runfo/tree/master/runfo) tool operates) to the developer on the pull request that initiated the build, via a GitHub Check (if possible). - -The information relayed back to the developer should note the following: -- link to an open, public GitHub issue if the failure appears to be known -- that the failure encountered was novel - -Examples of failure notes: -- `This test is also failing in the main branch` (link to open issue, if available) -- `This is a known Mac disconnect issue` (link to open issue, if available) - -This functionality may provide comparable data that is already provided by the runfo tool. In other words, if there is functionality that already exists in runfo that does what we need to do, we should look at implementing similar functionality in our own project. - -## Stakeholders -- Product teams' developers -- Product teams' management -- Engineering Services team's developers -- Engineering Services team's management - -This feature is primarily for the product teams, however, the Engineering Services team should dogfood their own functionality. - -## Risks - -- Data could be misleading and provide incorrect assumptions on cause of failures. (Because this functionality is new, we may not be able to create algorithms strong enough to sufficiently raise the signal-to-noise ratio experienced by devs.) -- Information relayed to the user may not contain actionable information (e.g. should they wait for other issues to be resolved? Do they need to fix a test? Is this something they need to report to Engineering Services team? et cetera) -- Information relayed to the user may be noisy. -- Ensure we are not hiding data that used to be visible - -### Proof of Concepts - -- Through a proof of concept, verify that we are able to build a service that will be able to accurately analyze the failure and provide relevant data (e.g. links to open GitHub issues regarding the failure) to the user regarding the failure. - - We will build a service that can take in an Azure DevOps build ID. This service will contain functionality that will detect failure patterns. Likely, this information will need to be provided by humans, so we will also need the ability to take in and store this information, such as in a JSON file. This should connect to a GitHub Checks API in order to report the failure analysis back to the user in the pull request. - - Additionally, we may want to build our POC as a command line tool initially, to ensure that the functionality works as expected before we integrate it with the infrastructure. - -- Through a proof of concept, verify that we are able to provide a mechanism for the repo owner to annotate their error messages so they can ignore non-useful error messages. - -### Dependencies - -- Helix Services -- Azure DevOps -- GitHub, GitHub Checks API -- Helix - -## Serviceability - -- Unit and functional (where appropriate, for both) tests for the individual components of the service -- Post-deployment scenario tests for the service to ensure that it is functioning as intended with all it's integrated parts. -- Algorithms developed for this feature should all be unit tested in order to detect errors if the algorithm changes. -- Ensure additional resources needed for this feature, such as additional data stores are well-documented for serviceability. - -### Rollout and Deployment - -- If the service is built in an existing project, such as Arcade Services or Helix Services, the rollout should be consistent with the existing rollouts for the project. - -## Usage Telemetry - -- Create a feedback mechanism (e.g. tracking image with a thumbs-up and thumbs-down) to include next to any communication so that the users can provide feedback quickly. -- Links created in new areas (e.g. GitHub comments, Failure Summary in GitHub Checks) could pass-through an aka.ms redirect link for us to capture usage from. - -## Monitoring - -- This new service will require usage and health monitoring. - -## FR Hand off - -- We will create user documentation for how to configure the retries and expectations for viewing results. -- We will create documentation for the Engineering Services team to investigate issues with the feature itself. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CFailure%20Guessing%20-%20arcade5963.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CFailure%20Guessing%20-%20arcade5963.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CFailure%20Guessing%20-%20arcade5963.md) - diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dev Workflow/Known Issues - arcade5963.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dev Workflow/Known Issues - arcade5963.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dev Workflow/Known Issues - arcade5963.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dev Workflow/Known Issues - arcade5963.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,68 +0,0 @@ -# Known Issues and Outage Reporting for Pull Requests - -We want to implement a notification that will be communicated to the developer on the pull request that initiated the build that notes any known outages that may cause or is causing failures. This will be similar to the Failure Guessing feature. - -Some of this data may already be available via [runfo](https://github.com/jaredpar/runfo/tree/master/runfo), so we should try to leverage the functionality that it has there, if it's useful for this feature. (e.g. recreating the functionality in our own code) - -Also, if possible, we'd like to automatically retry builds that have failed due to outages when the outage has been resolved. - -## Stakeholders -- Product teams' developers -- Product teams' management -- Engineering Services team's developers -- Engineering Services team's management - -This feature is primarily for the product teams, however, the Engineering Services team should dogfood their own functionality. - -## Risks - -- Data could be misleading and provide incorrect assumptions on cause of failures. -- Information relayed to the user may not contain actionable information (e.g. should they wait for other issues to be resolved? Do they need to fix a test? Is this something they need to report to Engineering Services team? et cetera) -- Information relayed to the user may be noisy. -- It's possible that the amount of work required for this future will overlap greatly with the greater Outage epic that is on the backlog. -- A manual process for tracking outages may mean issues that are tracking outages may be missed when the outage is resolved, or resolved prematurely. - -### Proof of Concepts - -- Through a proof of concept, verify that we are able to retry a build that had previously failed due to an outage. This will require us to track the builds that are affected by outages and then know when an outage has been resolved so that it can retry the failed builds. - - Additional things to consider: - - Is a retry for the build already in progress? - - Are there other things in the build that caused a failure that does not warrant a retry? - - Should only builds of a certain "age" be retried? (e.g. what if a build is a week old before a reported outage is marked as resolved?) - - Can a customer opt out of an automatic retry? (Would this make sense to have?) - -- Through a proof of concept, verify that we have a way of tracking outages that can be posted back to the pull request in GitHub via the GitHub Checks API to report this information back to the user. This may be a specific way of naming GitHub issues to track outages, or a service that will track the outages. - -### Dependencies - -- Helix Services -- Azure DevOps -- GitHub, GitHub Checks API - -## Serviceability - -- Unit and functional (where appropriate, for both) tests for the individual components of the service -- Post-deployment scenario tests for the service to ensure that it is functioning as intended with all it's integrated parts. - -### Rollout and Deployment - -- If the service is built in an existing project, such as Arcade Services or Helix Services, the rollout should be consistent with the existing rollouts for the project. - -## Usage Telemetry - -- Create a feedback mechanism (e.g. tracking image with a thumbs-up and thumbs-down) to include next to any communication so that the users can provide feedback quickly. -- Links created in new areas (e.g. GitHub comments, Failure Summary in GitHub Checks) could pass-through an aka.ms redirect link for us to capture usage from. - -## Monitoring - -- This new service will require usage and health monitoring. - -## FR Hand off - -- We will create user documentation for how to configure the retries and expectations for viewing results. -- We will create documentation for the Engineering Services team to investigate issues with the feature itself. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CKnown%20Issues%20-%20arcade5963.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CKnown%20Issues%20-%20arcade5963.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CKnown%20Issues%20-%20arcade5963.md) - diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dev Workflow/Passed On Rerun Data.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dev Workflow/Passed On Rerun Data.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dev Workflow/Passed On Rerun Data.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dev Workflow/Passed On Rerun Data.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,60 +0,0 @@ -# Test passed on rerun data - -Analyzing tests that passed on rerun at a granular level as is a build probably sometimes doesn't feel enough, or probably you want to see all the tests that passed on rerun in a quick look. - -For the cases in which you want to see all the tests that passed on rerun in your build or repo or look for the most common failures on the test that passed after a rerun, and many other scenarios, you can do the following: - -1. Query the PassedOnRerun data using the [AzureDevOpsTests](https://dataexplorer.azure.com/clusters/engsrvprod/databases/engineeringdata?query=.show%20table%20AzureDevOpsTests) table, located in: [Engsrvprod/engineeringdata](https://dataexplorer.azure.com/clusters/engsrvprod/databases/engineeringdata)
- - - ``` - AzureDevOpsTests - | where Outcome == "PassedOnRerun" - | where BuildId == [buildId] - | where Repository == [repository] - ``` - -1. Use [Build Analysis Reporting](https://msit.powerbi.com/links/crjYD5rwh0?ctid=72f988bf-86f1-41af-91ab-2d7cd011db47&pbi_source=linkShare) under `Test Passed on Rerun` tab to see aggregated data. - - -## Example of queries - -1. Query by build Id:
-To get the buildId of your build you can navigate to your Azure DevOps build pipeline, see the URL, it should look like this:
-`https://dev.azure.com/dnceng/public/_build/results?buildId=1234567&view=results`
-Look for the `buildId=` in this case is `1234567` and use that id in your query.
-Ex. - ``` - AzureDevOpsTests - | where Outcome == "PassedOnRerun" - | where BuildId == 1234567 - ``` - -2. Query by repository*:
-In this example you can see all the assemblies in the last week for a repository, ordered by passed on rerun count - ``` - AzureDevOpsTests - | where RunCompleted > ago(7d) - | where Outcome == "PassedOnRerun" - | where Repository == "dotnet/runtime" - | summarize count() by WorkItemFriendlyName - | order by count_ desc - ``` - * Examples of respositories: 'dotnet/roslyn', 'dotnet/runtime', 'dotnet/aspnetcore', 'dotnet/installer' - - -3. Query tests by assembly:
-In this example you can see all the tests in the last week for an assembly, ordered by passed on rerun count
- ``` - AzureDevOpsTests - | where RunCompleted > ago(7d) - | where Outcome == "PassedOnRerun" - | where WorkItemFriendlyName == "System.Net.Http.Functional.Tests" - | summarize count(), any(Arguments) by TestName, ArgumentHash - | order by count_ desc - ``` - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CPassed%20On%20Rerun%20Data.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CPassed%20On%20Rerun%20Data.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CPassed%20On%20Rerun%20Data.md) - diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dev Workflow/Retry Builds - arcade5963.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dev Workflow/Retry Builds - arcade5963.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dev Workflow/Retry Builds - arcade5963.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dev Workflow/Retry Builds - arcade5963.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,59 +0,0 @@ -# Automatically Retry Builds For Pull Requests - -We want to implement a mechanism in which builds that fail due to a reason that is defined in a configuration by the repository owners are automatically retried during pull request builds. - -The retry event will be communicated to the developer via GitHub on the pull request that initiated the build. - -## Stakeholders -- Product teams' developers -- Product teams' management -- Engineering Services team's developers -- Engineering Services team's management - -This feature is primarily for the product teams, however, the Engineering Services team should dogfood their own functionality. - -## Risks - -- Increase of resource costs because of retries, however, this should not be much more than the current cost it takes to re-run a build when a failure happens. -- Increase in time to investigate failures due to lag introduced by the retries. -- Rule management may become cumbersome. -- Low adoption-rate due to configuration requirements. - -### Proof of Concepts - -- Through a proof of concept, verify that we are able to retry a build when a retryable scenario (customer configured) is detected. See if we can leverage similar functionality that exists in Runfo today that scans the build logs when a failure occurs, and if the reason for failure matches any of the retryable scenarios, a retry is attempted on the build. This mechanism should be built as a separate service, and connected to the build processes in Azure DevOps via a webhook. - -- Through a proof of concept, verify that we are able to incorporate a mechanism for customers to quickly and easily provide feedback via some kind of tracking image. This mechanism must be able to work with markdown, and will provide: 1) a like or dislike of the feature; and 2) capture usage tracking information. This data should be captured in Application Insights so that we can query upon the data to see how it is being used and if customers are approve or disapproving of the feature. - -### Dependencies - -- Helix -- Arcade -- Azure DevOps -- GitHub, GitHub Checks API - -## Serviceability - -- Tests (unit, functional, E2E) for retry functionality - - A failed build for a retryable reason should be retried. - - A failed build for a reason that was not configured to be retried should not be retried. -- Tests/Validation for functionality we write to support the GitHub Check that will report on the retry status of a work item. - -### Rollout and Deployment - -- If all the functionality for this exists in Arcade, then the rollout for this functionality should only be impacted by Arcade promotions. We will need to ensure that there's sufficient testing (E2E) in Arcade Validation for this feature. -- Initial configuration and set up in the repositories will likely need assistance from the Engineering Services team to get the customers up and running. - -## Usage Telemetry - -- Create a feedback mechanism (e.g. tracking image with a thumbs-up and thumbs-down) to include next to any retry communication so that the users can provide feedback quickly. -- Links created in new areas (e.g. GitHub comments, Failure Summary in GitHub Checks) could pass-through an aka.ms redirect link for us to capture usage from. - -## FR Hand off - -- We will create user documentation for how to configure the retries and expectations for viewing results. -- We will create documentation for the Engineering Services team to investigate issues with the feature itself. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CRetry%20Builds%20-%20arcade5963.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CRetry%20Builds%20-%20arcade5963.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CRetry%20Builds%20-%20arcade5963.md) - diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dev Workflow/Retry Tests - arcade5963.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dev Workflow/Retry Tests - arcade5963.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Dev Workflow/Retry Tests - arcade5963.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Dev Workflow/Retry Tests - arcade5963.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,85 +0,0 @@ -# Automatically Retry Tests For Pull Requests - -We want to implement a mechanism in which work items that fail due to a reason that is defined in a configuration by the repository owners are automatically retried during pull request builds and tests. - -The work item can either be retried in the current environment, or, optionally, pushed to another machine to be retried. - -The retry will be communicated to the developer via GitHub on the pull request that initiated the test. We will ensure that failed attempts are not hidden from the user, and also communicated, as well. Full details of any failure shall be recorded. - -It is important to note that we can only retry based on a Helix Work Item since we are constrained by Azure DevOps and cannot retry on a per test basis. - -## Stakeholders -- Product teams' developers -- Product teams' management -- Engineering Services team's developers -- Engineering Services team's management - -This feature is primarily for the product teams, however, the Engineering Services team should dogfood their own functionality. - -## Risks - -- High implementation cost, however, the increase of green PRs due to automatically retrying failed tests would be a huge win. -- Increase of resource costs because of retries, however, retries per work item is a lower cost than a retry of an entire build and test suite that users do today. -- Increase in time to investigate failures due to lag introduced by the retries. -- Rule management may become cumbersome. -- Low adoption-rate due to configuration requirements. - -### Proof of Concepts - -- Through a proof of concept, verify that we are able to implement a retry mechanism in Arcade by enhancing the [Helix SDK scripts](https://github.com/dotnet/arcade/tree/main/src/Microsoft.DotNet.Helix/Sdk/tools/azure-pipelines/reporter) written in Python that will allow work items that fail and meet a certain criteria (defined in JSON) to be retried. - - Implementation details for this proof of concept are as follows: - - Customer configures their desired retry rules in a pre-defined json file that will be provided in the /eng/ folder within Arcade. - - When the build runs during the PR, if a work item fails that matches the rules defined in the json file, we will automatically retry that work item. - - When the retry is initiated, a comment will be posted on the pull request that initiated the build/test suite remarking the failure and subsequent retry event. This information should also be communicated to the Failure Summary page through GitHub Checks. - - The test will be retried the specified number of times as noted in the configuration, depending on subsequent failures. - -- Through a proof of concept, verify that we are able to incorporate a mechanism for customers to quickly and easily provide feedback via some kind of tracking image. This mechanism must be able to work with markdown, and will provide: 1) a like or dislike of the feature; and 2) capture usage tracking information. This data should be captured in Application Insights so that we can query upon the data to see how it is being used and if customers are approve or disapproving of the feature. - -### Dependencies - -- Helix -- Arcade -- Azure DevOps -- GitHub, GitHub Checks API - -## Serviceability - -- Tests (unit, functional, E2E) for Python components in Arcade that will interact with Helix. (Provided the aforementioned proof of concept is acceptable, or for whatever mechanism is eventually written to support this functionality) - - Tests for retry functionality on same machine - - Tests for retry functionality sent to another machine -- Tests/Validation for functionality we write to support the GitHub Check that will report on the retry status of a work item. -- SDL considerations will need to be made for sentiment tracking. - -### Rollout and Deployment - -- If all the functionality for this exists in Arcade, then the rollout for this functionality should only be impacted by Arcade promotions. We will need to ensure that there's sufficient testing (E2E) in Arcade Validation for this feature. -- Initial configuration and set up in the repositories will likely need assistance from the Engineering Services team to get the customers up and running. - -## Usage Telemetry - -- Create a feedback mechanism (e.g. tracking image with a thumbs-up and thumbs-down) to include next to any retry communication so that the users can provide feedback quickly. -- Links created in new areas (e.g. GitHub comments, Failure Summary in GitHub Checks) could pass-through an aka.ms redirect link for us to capture usage from. -- Usage telemetry: - - Did the retry result in a passing test? - - What triggered the retry? - - Which work item did it trigger on? - - How many retries were triggered for this work item? - -## Monitoring - -- The sentiment feature will need monitoring (e.g. sudden and/or excessive negative feedback may indicate something is wrong with a feature and is getting negative feedback from customers). -- The retry functionality will need monitoring. - - Does this increase CPU or resource usage? - - Get a baseline of resource usage today before feature is implemented. - - Other cost-related monitoring - - Detect if the functionality is down/broken (e.g. a retry should've been triggered, but it was not) - -## FR Hand off - -- We will create user documentation for how to configure the retries and expectations for viewing results. -- We will create documentation for the Engineering Services team to investigate issues with the feature itself. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CRetry%20Tests%20-%20arcade5963.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CRetry%20Tests%20-%20arcade5963.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CDev%20Workflow%5CRetry%20Tests%20-%20arcade5963.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/docker-image-usage-improvements.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/docker-image-usage-improvements.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/docker-image-usage-improvements.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/docker-image-usage-improvements.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,76 +0,0 @@ -# Background # -Several of the .NET Core repositories utilize Docker images within their official build definitions for the supported Linux distros. Utilizing Docker allows a single Linux configuration to be used on all of the build agents yet the product can be built on all of the supported distros. Using Docker also allows the product teams to easily manage the build prereqs because they specified within the Dockerfiles and don't require VSO service engineers in order to change them. - -The following is a summary of the prescribed process currently being used for adding/updating the Docker images. - -1. Add a new or update the existing [DockerFiles](https://devdiv.visualstudio.com/DevDiv/_git/DotNetCore?path=%2Fdockerfiles&version=GBmaster&_a=contents) as necessary. -2. Build the new/modified Dockerfiles locally. Tag the image using the following naming schema `_prereqs_v` (e.g. `ubuntu1610_prereqs_v3`) -3. Verify the new/modified image works as expected. -4. Get someone with the appropriate access to push the new/modified image to [Docker Hub](https://hub.docker.com/r/chcosta/dotnetcore/). -5. Update the appropriate build pipelines to reference the new Docker images (e.g. [CoreFx build pipeline](https://github.com/dotnet/corefx/blob/94780d59037393369d22def54466b2e13d81c435/buildpipeline/pipeline.json)) - -# Problems # - -## Out of Date or Missing Dockerfiles ## -Some of the images we are currently using within our build definitions do not have the corresponding Dockerfiles they were generated from checked into SCC. - -1. Some images were created by running a base OS image and then manipulating it as necessary in order to add the required tools and dependencies. The resulting images were then captured using the [docker commit](https://docs.docker.com/engine/reference/commandline/commit/) command. -2. Some images were created via Dockerfiles that were never checked into SCC. -3. Some Dockerfiles were updated to produce newer versions of images but the updated Dockerfiles were never checked into SCC. - -Because we do not have the Dockerfiles for the images we are building with today, we don't really know with certainty what dependencies and toolsets we are building the product with. You can gather this information by inspecting the images we are building with but it is a time intensive process and prone to oversight. More importantly it is possible for the images on Docker Hub to be accidentally replaced or deleted. If this were to happen we would be in a very bad situation and would have to scramble to get the Docker images recreated. - -## Docker Image Versioning ## -Nothing in the process used today enforces versioning of the Docker images. As described earlier, it is up to the individuals that have access to the Docker Hub repository to increment the Docker image version whenever an update is made. Hopefully this is being followed but it is susceptible to human judgement and error. - -If this process is not being followed, it is possible that updating an image without incrementing the version (e.g. changing the tag) would break the various builds that utilize the shared images. This issue may not surface itself immediately. For example it may only get surfaced at the time a previous release is serviced. Tracking down issues like this can be very time consuming and wasteful. - -## Docker Image Traceability ## -There is no way to trace back from a Docker image to the Dockerfile it was generated with. For example, suppose we release version 1.0 of a product that was built with version 4 of a particular Docker image. A couple months pass and a service patch is needed for the release. For this particular service fix, a new version of a tool is needed. How do we correlate a Docker image back to the specific version of the Dockerfile it was generated from in order to update the required tool? There isn't a way to correlate a Docker image tag/version to a specific Dockerfile without manually inspecting what is installed on the image in relation to the file history of the Dockerfile. - -Another problem that has surfaced with our Dockerfiles is that they do not reference a static OS base image. The base OS images do get revised overtime with service patches. This has causes issues such as [dotnet/core-setup#1149](https://github.com/dotnet/core-setup/pull/1149). We should be explicitly making these changes and verifying the changes prior to rolling them into production. - -## Docker Hub Repository ## -Currently the Docker images being used are stored in Chris Costa's personal [Docker Hub repository](https://hub.docker.com/r/chcosta/dotnetcore/). Relying on personally owned artifacts is not a good practice to use. Chris could leave the team, company or worse, which may lead to issues in administering these artifacts. - -## Docker Toolset ## -The Docker toolset being used by the builds is not captured anywhere. Docker may introduce breaking changes or behavior changes over time that could be detrimental if introduced onto the build agents. Docker has been known to do this in the past (e.g. [Regression in LTTng behavior on Docker 1.10.2](https://github.com/docker/docker/issues/20818)). When the product is built, it should be using an explicit version of the Docker toolset so that we can ensure repeatability and reliability. - -# Proposed Changes # - -## Automated Builds ## -Introducing automated builds would a great way to ensure we will always have the source Dockerfiles for the Docker images we use. The only way to update a Docker image would be to update the corresponding Dockerfile. When a Dockerfile change is merged in, a build would get kicked off automatically that would build the Docker image and upload it to Docker Hub. The build definition would use a service account to upload images to Docker Hub. This service account would be the only account that would have access to upload images. Therefore the only way to update the Docker images would be to make a change to the checked-in Dockerfiles. - -VSTF build definitions would provide the necessary functionality and flexibility to meet our requirements. Key vault should be utilized to store the credentials so that they are stored in a centralized place for servicing. If needed in a pinch, the credentials could be used manually to upload an image. - -## Tagging Scheme ## -In order to provide traceability between a Docker image tag and the Dockerfile it was produced with, we should utilize a tagging scheme similar to the following: - -`.---` - -**Examples** - -- `opensuse.42.1-20170118-c760fcc` -- `Ubuntu.14.04-crossbuild-20161210-b04b497` - -The automated builds would be capable of generating the tag from the Dockerfile location in SCC and the commit that triggered the build. A timestamp is suggested in addition to a commit sha simply as a means to quickly tell how old an image is and compare two tags in order to tell which one is older. - -## Docker Repository ## -A new Docker repository should be used for our Docker images that is owned by an organization (e.g. `microsoft`). This will ensure that the dotnet organization will always have control over the repository as team members come and go. If we continue to use Docker Hub, we don't want a repository that could distract users from the official .NET Docker repository (e.g. `microsoft/dotnet`). Something like `microsoft/dotnet-buildtools-prereqs` could do that. If we wanted to obfuscate it more in order to avoid having the general public find it when looking for the real dotnet images, we could name it `microsoft/dnbpr` which would stand for `dotnet build prereqs`. This becomes a little unnatural. Couple this naming issue with the desire we have to be able to build the product without taking a dependency on non-MSFT services, we should use a private Docker registry. Azure currently has beta support for a [container registry](https://azure.microsoft.com/en-us/services/container-registry/) which would meet our needs. - -Using a custom Docker registry is pretty easy. You don't use the Azure CLI, you still use the Docker CLI. The differences are that first you must explicitly login to the private registry (`docker login dotnetcore-microsoft.azurecr.io -u *** -p ***`). Second you must include the registry name in all of the image requests such as run, pull, etc. (`docker pull dotnetcore-microsoft.azurecr.io/build_prereqs:ubuntu.14.04`) - -## Reference Base Images via Digest ## -In order to solve the issues with the base images changing overtime due to service fixes, we should be [referencing the base images via digest](https://docs.docker.com/engine/reference/builder/#/from). This change makes the Dockerfile less readable by itself therefore it is recommended that we add a comment that clearly states what the base image is and the date it was produced. - -## Versioned Docker Toolset ## -The solution for capturing the Docker toolset required by our builds and the mechanisms used to automatically acquire them are being covered by the work Matt Mitchell (Versionable Environments) and Ravi Eda ([Cmake versioning](https://github.com/dotnet/arcade/blob/main/Documentation/Project-Docs/cmake-scenarios.md)) are defining. It is sufficient for the scope of this document to say that whatever pattern comes out of this work should be applied to the Docker toolset. - -## Move Dockerfiles to Open ## -Work has been going on recently to check-in the build definitions into the product repositories (e.g. [corefx](https://github.com/dotnet/corefx/tree/master/buildpipeline)). These build definitions reference our Docker images. Because of this, it would be beneficial to move the Dockerfiles from the [private repository](https://devdiv.visualstudio.com/DevDiv/_git/DotNetCore?path=%2Fdockerfiles&version=GBmaster&_a=contents) into the open. There are no trade secrets and they could be useful for others to see. A natural place to put these shared Dockerfiles would be within the [buildtools repo](https://github.com/dotnet/buildtools). - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5Cdocker-image-usage-improvements.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5Cdocker-image-usage-improvements.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5Cdocker-image-usage-improvements.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/fetch-internal-tooling.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/fetch-internal-tooling.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/fetch-internal-tooling.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/fetch-internal-tooling.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -# Fetch Optional (Internal) Tooling - -This is an implementation plan for how to fetch sensitive internal tools during a .NET Core build. - -## Uploading a new tool - -The tool is put in a NuGet package and uploaded to a VSTS feed. VSTS feeds require authentication for any operation, and are secure. - -## Fetching during the build - -**To fetch internal tooling in your local dev build, see the [Running CoreFx tests on UAP (CoreCLR scenario) OneNote page -](https://microsoft.sharepoint.com/teams/netfx/corefx/_layouts/OneNote.aspx?id=%2Fteams%2Fnetfx%2Fcorefx%2FDocuments%2FCoreFx%20Notes&wd=target%28Engineering%2FNet%20Standard%202.0.one%7CD8792BD0-63D5-4D0F-8EF0-B0F8444F49CD%2FRunning%20CoreFx%20tests%20on%20UAP%20%28CoreCLR%20scenario%5C%29%7C48A101A6-5621-4131-A49C-DA95C155D126%2F%29)** - -An `optional-tool-runtime/project.json` file in BuildTools specifies all required tooling that is only available from the internal VSTS feed. This is similar to [`tool-runtime/project.json`](https://github.com/dotnet/buildtools/blob/6a1400e631a097587246e973973e9fafe7ab6254/src/Microsoft.DotNet.Build.Tasks/PackageFiles/tool-runtime/project.json). - -In the official build, three properties are set for the `sync` call: - -``` -OptionalToolSource=https://devdiv.pkgs.visualstudio.com/_packaging/dotnet-core-internal-tooling/nuget/v3/index.json -OptionalToolSourceUser=dn-bot -OptionalToolSourcePassword=****** -``` - -A target in BuildTools runs before the main project package restore, detects that these properties are set, then restores `optional-tool-runtime/project.json` into the `packages` directory. Build steps that need an optional tool can find it using `PrereleaseResolveNuGetPackageAssets`. - -The path to the project file can be overridden to specify repo-specific tooling, like in CoreFX: [dir.props#L303](https://github.com/dotnet/corefx/blob/30a0f7f753162b89ad110b4beba3fdeda434fe8c/dir.props#L303), [optional.json](https://github.com/dotnet/corefx/blob/30a0f7f753162b89ad110b4beba3fdeda434fe8c/external/test-runtime/optional.json). - -Devs who have the optional tooling packages but don't have convenient access to the VSTS feed can set `OptionalToolSource` to a directory to use it as an optional tool package feed. - -If `OptionalToolSource` isn't set, no optional tooling is restored. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5Cfetch-internal-tooling.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5Cfetch-internal-tooling.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5Cfetch-internal-tooling.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/helix-metrics.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/helix-metrics.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/helix-metrics.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/helix-metrics.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,124 +0,0 @@ -# Metrics - -## Overview -We want to unify our reporting infrastructure so that we have a centralized way to report on metrics related the health and behavior of the helix services. -We'll have a single place for defining all the interesting information about a metric (it's names, which services it applies to, levels it should be alerted at...), -which we'll generate all the pieces we need. - * Strongly typed wrappers to send metrics - * Automatically deployed alerting rules - * Visualizations -These metrics will either be sent by the services themselves, or an external monitoring service. - -## Unified File -We will have a centralized file to define all our metrics, something similar to -``` YAML -services: - - missionControl - - helixApi -instances: - - helix - - missionControl - - helixClient -metrics: - - - name: DataMigrationQueueDepth - instance: helix - servicesAffected: - - missionControl - - helixApi - warning: >100 - error: >1000 - alertIfMissing: true - -``` -This will define the services we monitor, the application insights instances that we are reporting to/from, and then a list of all metrics that -we're report and alert. - -## Code Generation -From the unified file, we'll run some pre-processing code to generate C# (and potentially other language) wrappers for reporting, -``` C# -public class HelixMetric -{ - public void Track(double value); -} - -public class HelixReportingProvider -{ - public HelixMetric DataMigrationQueueDepth { get; } -} - -... - -private HelixReportingProvider _reporting; // DI this - -public void SomewhereElse() -{ - _reporting.DataMigrationQueueDepth.Track(900); -} -``` - -We'll handle pre-aggregation based on recommendations -[here](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-api-custom-events-metrics#trackmetric). -The metrics/events will be reporting to AI as customMetrics and customEvents -If we need wrappers for other languages, we can work on that. - -The wrappers will be generated at build time, so there won't be a need to check them in, hopefully. - -## Monitoring Service -Most monitoring can be handled by the services themselves, for example, when pulling an item from an Azure queue, it's trivial -to report on the age of the item at that point (since it's part of the pulled item). - -However, sometimes this isn't true. For example, reporting on the queue _depth_ isn't something that's easily handled at pull time, -and it shouldn't be done by every pulling instance, since it would just cause noise (there is only one queue depth to report). -These will be handled by a separate monitoring service that runs and monitors external factors, like queue depth, -or reachability of storage accounts, or such things. - -## Reporting -We're going to use Power BI to start and see if that's a good fit for our alerting. Hopefully we can make it data -driven as well, so that it can pull from our source file with some transformation. - -If Power BI doesn't work out, we'll use custom graphing in Mission Control, pulling from AI - -## Outage Service -Knowing a metric is off is great for our ability to run our service, but an important part of a service is the ability -to report out to users about downtime and outages. - -To this end, we need to create a fairly simple outage reporting service. It doesn't need to be much more than -a table that lists services, any outages, and any notes that we have made about it's status. - -There should be an API listening here that AI alerts can report to. When that happens, we should open -some sort of ticket (probably a github issue) so that it can be tracked/resolved. - -This won't consist of more than a fairly simple Azure Table, and a handful of webpages (overall status, -report new outage, resolve existing outage). - -## Alerting -Application Insights already has fairly good alerting based on custom metrics, which we will take advantage of. -Using the single file, a resource template will be created, as shown -[here](https://docs.microsoft.com/en-us/azure/monitoring-and-diagnostics/monitoring-enable-alerts-using-template). -It will both email the outage to an alias (maybe dnceng) and notify the outage service so it can open an issue -and possible mark an outage. - -All of these alerts will alert to the same email address and also report to an alerting service. - -## What to Measure -In short: whatever it's feasible to measure. Having a metric reporting should be very low cost to the service, -as long as it's not in a very tight inner loop, so over-reporting should be prefered to under-reporting. - -Some examples of things that we should measure ... - * ... in any sort of producer/consumer model ... - * ... the average production and consumption rates - * ... the current depth of the backlog - * ... the average delay between production and consumption - * ... in any user exposed service ... - * ... availablity - * ... average response times for a fixed, known piece of work - * ... in a destributed service ... - * ... heartbeats inside the code that's doing work - * ... of any external service we depend on - * ... availability query (can we contact the service with the credentials we expect) - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5Chelix-metrics.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5Chelix-metrics.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5Chelix-metrics.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/HelixTestLogSearch/instructions.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/HelixTestLogSearch/instructions.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/HelixTestLogSearch/instructions.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/HelixTestLogSearch/instructions.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,88 +0,0 @@ -# How to use Helix test log search - -This functionality is accessible through an endpoint in Helix Services at `/logs/search` - -## Input - -All of the following are required parameters: -- `repository`: The error string the program will search for -- `searchString`: The public repository whose test logs will be parsed. The program will only search for non-internal test logs. -- `startDate`: The start date of the date range in which the test logs will be searched. This argument must be in the format yyyy/MM/dd. For example, 2022-05-10. The date must be within the last 14 days. -- `endDate`: The end date of the date range in which the test logs will be searched. This argument must be in the format yyyy/MM/dd. For example, 2022-05-10. The date must be within the last 14 days. -- `responseType`: This argument is one of "Hits" or "HitsPerFile". The string must exactly match. This determines the type of response returned (sample responses can be found in the Outputs section below). - -Note that the `repository` and `searchString` values should be URL encoded when they are passed in through the browser. - -Example: - - http://localhost:8080/logs/search/dotnet%2Fruntime/Remote%20process%20failed%20with%20an%20unhandled%20exception./2022-07-14/2022-07-21/HitsPerFile/ - -## Output - -### Hits -Here is sample output for a `Hits` response. Each item in the hits array corresponds to a string match in a log file. - - { - "filter": { - "repository": "dotnet/runtime", - "errorString": "Microsoft.DotNet.RemoteExecutor.RemoteExecutionException : Remote process failed with an unhandled exception.", - "startDate": "2022-07-15T07:00:00Z", - "endDate": "2022-07-21T07:00:00Z", - "responseType": "Hits" - }, - "hits": [ - { - "lineContent": "Microsoft.DotNet.RemoteExecutor.RemoteExecutionException : Remote process failed with an unhandled exception.", - "lineNumber": 29, - "jobId": 20180433, - "friendlyName": "System.Net.Security.Tests", - "status": "Fail", - "started": "2022-07-15T12:00:20.807Z", - "finished": "2022-07-15T12:01:18.036Z", - "consoleUri": "https://helixre107v0xdeko0k025g8.blob.core.windows.net/dotnet-runtime-refs-heads-release-50-0a95b79667f444c8ac/System.Net.Security.Tests/1/console.a9847084.log?helixlogtype=result", - "queueName": "osx.1015.amd64.open", - "attempt": 1 - }, - ... - ], - "occurrenceCount": 4, - "filesCount": 4 - } - - -### HitsPerFile -Here is sample output for a `HitsPerFile` response. Each item in the hits array corresponds to one log file. - - { - "filter": { - "repository": "dotnet/runtime", - "errorString": "Microsoft.DotNet.RemoteExecutor.RemoteExecutionException : Remote process failed with an unhandled exception.", - "startDate": "2022-07-15T07:00:00Z", - "endDate": "2022-07-21T07:00:00Z", - "responseType": "HitsPerFile" - }, - "hits": [ - { - "occurrences": 1, - "jobId": 20214225, - "friendlyName": "System.Net.Security.Tests", - "status": "Fail", - "started": "2022-07-20T12:04:48.094Z", - "finished": "2022-07-20T12:05:47.138Z", - "consoleUri": "https://helixre107v0xdeko0k025g8.blob.core.windows.net/dotnet-runtime-refs-heads-release-50-db3c3d858764452e9d/System.Net.Security.Tests/1/console.57a6404e.log?helixlogtype=result", - "queueName": "osx.1015.amd64.open", - "attempt": 1 - }, - ... - ], - "occurrenceCount": 4, - "filesCount": 4 - } - - -## Other notes -- There is a timeout of 120 seconds/2 minutes on the whole program. If the query and parsing time out, the program throws an error and will not return any data. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CHelixTestLogSearch%5Cinstructions.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CHelixTestLogSearch%5Cinstructions.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CHelixTestLogSearch%5Cinstructions.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/HelixTestLogSearch/one-pager.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/HelixTestLogSearch/one-pager.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/HelixTestLogSearch/one-pager.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/HelixTestLogSearch/one-pager.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,144 +0,0 @@ -# ✔️ Introduction -## Requirements -We want to create a REST API that allows users to find the frequency of a specific error string in a repository's Helix test logs. So we want to be able to query an endpoint with a `repository`, `error_string`, `start_date`, and `end_date` to get a list of all the error occurrences of the string in the build logs matching those arguments. - -# 🖥️ Implementation -## High-level diagram -Project diagram - -## Logic -1. Take and sanitize user input -2. Execute Kusto query (filter by repository, date_ranges, fail status) -3. Iterate through Kusto's results to retrieve log files' path in Azure Storage -4. Read log file's content line by line using file stream -5. Use string searching/matching to find error string in the lines. -6. If match is found, keep track of it and increment occurrences found. -7. Return the results found as a JSON object. - -### Kusto query - Jobs - | where Repository == REPO_NAME - | project JobId, IsExternal - | join kind = inner WorkItems on JobId - | project JobId, FriendlyName, Status, Started, Finished, ConsoleUri, QueueName, Attempt, IsExternal - | where Status == 'Fail' - | where Started between(START_DATE .. END_DATE) | where IsExternal == 1"; - -## Input -- Arguments - - `repository` - - `error_string` - - `start_date` - - `end_date` - -- Constraints - - `repository` must be an existing, public repository. Its spelling must match the repo name exactly. - - `error_string` should probably have some kind of limit on length. - - The duration between `start_date` and `end_date` should have a maximum of 7 days). If user input exceeds this value, one possible way of handling this is to just query jobs between our defined max number of months before the given `end_date` and alert the user that this was done instead of their original query. - -## Dependencies -- Kusto -- Azure Account Storage - -## String Matching -The three possible string matching methods ranked by speed/performance are: -1. C# `String.contains`, `String.replace`, etc -2. Boyer-Moore string searching algorithm -3. Regex - -This ranking is based on the following articles: - -[Boyer-Moore VS String.contains](http://www.blackbeltcoder.com/Articles/algorithms/fast-text-search-with-boyer-moore) - -**TLDR;** Although Boyer-Moore is considered one of the fastest string-matching algorithms, C#'s `String.contains` method is faster as it uses assembly optimization. Although we might need a performance test since we will need to go line by line and load the strings from each log file if we use `String.contains` and that might take even longer. - -[String.contains VS Regex.isMatch](https://theburningmonk.com/2012/05/performance-test-string-contains-vs-string-indexof-vs-regex-ismatch/#:~:text=As%20you%20can%20see%2C%20Regex.IsMatch%20is%20by%20far,turned%20out%20to%20be%20significantly%20faster%20than%20String.IndexOf.) - -**TLDR;** Regex matching is way slower than String methods. It's only more useful if we want to pattern match as opposed to finding a fixed string. (Actually this raises the question - do we want to pattern match?) - -Also, this article [Fastest Ways to Count Substring Occurences in C#](https://cc.davelozinski.com/c-sharp/c-net-fastest-way-count-substring-occurrences-string) compares the speeds of different methods of counting substring occurences. - -**TLDR;** Using BCL was the fastest method for the following performance tests: -> Counting the number of times 1 string occurs in 5,000, 25,000, 100,000, and 1,000,000 million strings. -> -> Counting the number of times 100 strings occur in 5,000, 25,000, 100,000, and 1,000,000 million strings. -> -> Counting the number of times 1,000 strings occur in 5,000, 25,000, 100,000, and 1,000,000 million strings. - -It also corroborates the article saying Regex matching is very slow for long strings. - -**❕ Decision is to use `String.Contains` (BCL) for now and stick with fixed string matching. Notes were made in additional features section to possibly include pattern matching down the road.** - -## File Reading -Since we will potentially need to be reading text from thousands of files, it's worth taking a look at fastest ways to read file input. The following article benchmarks the time it takes for different ways of reading file input. - -[Fastest Ways to Read Text Files in C#](https://cc.davelozinski.com/c-sharp/fastest-way-to-read-text-files) - -**TLDR;** There was no one fastest method found, but in general, reading line by line and storing each line into a string was fast, and should be sufficient for this program. We can also make it faster using parallel threads if needed. - -**❕ We want to read different log files async using some version of `Task.WhenAll` to read the files concurrently.** - -## Output -#### Possible JSON output: - { - "filter": { - "repository": "...", - "error_string": "", - "start_date": "", - "end_date": "", - "num_hits": 00, - }, - "hits": - [ - { - "document_uri": "uri to document", - "job_id": "helix guid" - "friendly_name": "", - "started": "", - "finished": "", - "queue_name": "", - }, - { - "document_uri": "uri to document", - "job_id": "helix guid" - "friendly_name": "", - "started": "", - "finished": "", - "queue_name": "", - }, - ... - ] - } - -# 👓 Proof-of-Concept -The plan for now is reading log files line by line and using `String.Contains`. We also want to use `Tasks` to parse each log file in parallel. Currently, I’m taking the following steps to implement POC: - -1. Write code for parsing a file using a hardcoded URI and getting in the data we want to return -2. Replace the hardcoded URI with the actual URIs retrieved from a Kusto Query and looping through multiple URIs (and eventually the thousands that are actually returned). -3. Deploy the POC so that we can run it on the same data centre that the logs are stored so we can see the actual speed of the program - -- Will test out string matching on a fixed number of log files first to see the speed on a local machine (and we also want to see the speed of actually running it on servers) - -# 📓 Additional Notes - -### Possible additional features -- Include line number and character index that a string match was found -- Allow user to pass either/or these 2 options as arguments: - - `repository`, `error_string`, `date_range` - - `build_id` list -- Taking an optional parameter for context lines (e.g also return the 5 lines surrounding the hit line - think GDB) -- Allowing pattern matching using regex (currently only allow for fixed string matching) -- Allow user to pass token to authenticate and allow search in non-external jobs -- Include retries in the output (`Attempt` and `LocalIteration` columns from `Files` table) - -### Issues/questions to look into down the road -- Possibly use a profiler (like VS profiler) to look more into performance -- Eventually we want to deploy to use the same data centres as the logs in Azure -- Look more into handling failure cases like limiting user input i.e only 1 outstanding request allowed per person also “(limiting the input sizes, like only X total days, or Y total logs to scan), returning a partial result if we run out of time, a stateful server request, where you could ask "hey, I started this query a bit ago, do you have the answer yet"... Lots of exciting options! -- Keep in mind the constraints for date range input - for now we are using a 7 day max duration but this can be changed if it is actually faster than expected - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CHelixTestLogSearch%5Cone-pager.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CHelixTestLogSearch%5Cone-pager.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CHelixTestLogSearch%5Cone-pager.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/HelixTestLogSearch/poc-summary.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/HelixTestLogSearch/poc-summary.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/HelixTestLogSearch/poc-summary.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/HelixTestLogSearch/poc-summary.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,141 +0,0 @@ -# Helix Test Log Search POC Summary -To recall, this POC queries Kusto for failed test logs for a specified repository and date range, and parses these logs line by line for a given string. - -In general, since we are limiting the number of days a user can search for a string, depending on the number of files that are returned by the Kusto query, the program will run for anywhere between 20s and 1m30s. - -## 📋 How to use -The program requires all arguments to be passed when running it in command line: - - dotnet run --errorString={SEARCH_STRING} --repository={REPOSITORY_NAME} --startDate={STARTING_DATE_RANGE} --endDate={ENDING_DATE_RANGE} --mode={RESULT_TYPE} - -See the Input section below for more details on input constraints. - - -### Input -Run the console app in terminal and pass all of the following options: - -- `errorString`: The error string the program will search for -- `repository`: The public repository whose test logs will be parsed -- `startDate`: The start date of the date range in which the test logs will be searched. This argument must be in the format **yyyy/MM/dd** and is in UTC. -- `endDate`: The end date of the date range in which the test logs will be searched. This argument must be in the format **yyyy/MM/dd** and is in UTC. -- `mode`: Either `"Hits"` or `"HitsPerFile"`. Hits will return an array of all occurences of `errorString`, including each occurrence's line number within its test log file. HitsPerFile will return an array of hits per file, including the count of occurrences per file. - -#### Example - - dotnet run --errorString="UseCallback_BadCertificate_ExpectedPolicyErrors" --repository=dotnet/runtime --startDate=2022-06-01 --endDate=2022-06-07 --mode=HitsPerFile - -This command will search for all instances of the string "UseCallback_BadCertificate_ExpectedPolicyErrors" in all the `dotnet/runtime` Helix test logs created between June 1, 2022 and June 7, 2022. It will return the number of hits found per file. - -### Output - -Here is some sample output showing the 2 different response types. - -#### Hits - { - "filter": { - "repository": "dotnet/runtime", - "errorString": "UseCallback_BadCertificate_ExpectedPolicyErrors", - "startDate": "2022-05-25T07:00:00Z", - "endDate": "2022-06-07T07:00:00Z" - }, - "hits": [ - { - "lineContent": "System.Net.Http.Functional.Tests.SocketsHttpHandler_HttpClientHandler_ServerCertificates_Test.UseCallback_BadCertificate_ExpectedPolicyErrors(url: \"https://wrong.host.badssl.com/\", expectedErrors: RemoteCertificateNameMismatch) [FAIL]", - "lineNumber": 129, - "jobId": 19894144, - "friendlyName": "System.Net.Http.Functional.Tests", - "status": "Fail", - "started": "2022-06-01T12:36:28.573Z", - "finished": "2022-06-01T12:42:46.786Z", - "consoleUri": "https://helixre107v0xdeko0k025g8.blob.core.windows.net/dotnet-runtime-refs-heads-release-50-159e0decb4474dbfbb/System.Net.Http.Functional.Tests/1/console.52339d8a.log?helixlogtype=result", - "queueName": "ubuntu.1804.armarch.open", - "attempt": 1 - }, - { - "lineContent": "/_/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs(334,0): at System.Net.Http.Functional.Tests.HttpClientHandler_ServerCertificates_Test.UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(String url, String useHttp2String, SslPolicyErrors expectedErrors)", - "lineNumber": 142, - "jobId": 19894144, - "friendlyName": "System.Net.Http.Functional.Tests", - "status": "Fail", - "started": "2022-06-01T12:36:28.573Z", - "finished": "2022-06-01T12:42:46.786Z", - "consoleUri": "https://helixre107v0xdeko0k025g8.blob.core.windows.net/dotnet-runtime-refs-heads-release-50-159e0decb4474dbfbb/System.Net.Http.Functional.Tests/1/console.52339d8a.log?helixlogtype=result", - "queueName": "ubuntu.1804.armarch.open", - "attempt": 1 - }, - ... - ], - "occurrenceCount": 96, - "filesCount": 24 - } - -#### Hits per File - { - "filter": { - "repository": "dotnet/runtime", - "errorString": "UseCallback_BadCertificate_ExpectedPolicyErrors", - "startDate": "2022-05-25T07:00:00Z", - "endDate": "2022-06-07T07:00:00Z" - }, - "hits": [ - { - "occurrences": 4, - "jobId": 19902082, - "friendlyName": "System.Net.Http.Functional.Tests", - "status": "Fail", - "started": "2022-06-02T12:16:47.584Z", - "finished": "2022-06-02T12:21:47.023Z", - "consoleUri": "https://helixre107v0xdeko0k025g8.blob.core.windows.net/dotnet-runtime-refs-heads-release-50-ab8bca7d45fc4cd0bb/System.Net.Http.Functional.Tests/1/console.e36c2801.log?helixlogtype=result", - "queueName": "ubuntu.1804.armarch.open", - "attempt": 1 - }, - { - "occurrences": 4, - "jobId": 19909540, - "friendlyName": "System.Net.Http.Functional.Tests", - "status": "Fail", - "started": "2022-06-03T13:37:32.456Z", - "finished": "2022-06-03T13:43:35.135Z", - "consoleUri": "https://helixre107v0xdeko0k025g8.blob.core.windows.net/dotnet-runtime-refs-heads-release-50-e3fe329caffc4ab3be/System.Net.Http.Functional.Tests/1/console.76909294.log?helixlogtype=result", - "queueName": "ubuntu.1804.armarch.open", - "attempt": 1 - }, - ... - ], - "occurrenceCount": 547, - "filesCount": 17 - } - - -## 🔍 Performance findings - -We can take a look at how long the program ran for different volumes of logs retrieved and parsed, as well as for different lengths of the search string. - -For one given repo and a one word string, we found: - -| Start date | End date | # of Occurrences | # of Files with hits | Total time elapsed | Lines scanned/sec | Files scanned/sec | Total files scanned -| ----------- | ----------- |-------------- | --------------------- | ------------------ | ----------------- | ----------------- | ------------------ | -| 2022/06/01 | 2022/06/07 | 2528 | 20 | 00:00:31.7819027 | 2532.73 | 0.69 | 22 -| 2022/05/25 | 2022/06/07 | 22768 | 510 | 00:00:35.2533931 | 41198.75 | 14.60 | 515 -| 2022/05/07 | 2022/06/07 | 124973 | 3023 | 00:00:41.9848476 | 193610.51 | 79.95 | 3357 - -For one given repo and a string with 6 words: - -| Start date | End date | # of Occurrences | # of Files with hits | Total time elapsed | Lines scanned/sec | Files scanned/sec | Total files scanned -| ----------- | ----------- |-------------- | --------------------- | ------------------ | ----------------- | ----------------- | ------------------ | -| 2022/06/01 | 2022/06/07 | 547 | 17 | 00:00:31.2928707 | 2572.31 | 0.70 | 22 -| 2022/05/25 | 2022/06/07 | 6106 | 124 | 00:00:28.0314279 | 51813.12 | 18.37 | 515 -| 2022/05/07 | 2022/06/07 | 16431 | 310 | 00:00:49.0204510 | 165822.79 | 68.48 | 3357 - -Note that the time taken can vary based on the repository, date range, network, etc so this is just a snapshot of a couple of times of running the program. But in general the times are pretty consistent and stay under a minute. - -## 🤔 Future considerations -- More input sanitization to limit number of rows returned by Kusto and avoid timeouts, also to avoid injections into the query -- Error handling for if we try to parse a file that doesn't exist on the server anymore -- Before implementing functionality to accept a token from the user to access non-public work item logs, consider how to handle when a user enters a private repo - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CHelixTestLogSearch%5Cpoc-summary.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CHelixTestLogSearch%5Cpoc-summary.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CHelixTestLogSearch%5Cpoc-summary.md) - Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/HelixTestLogSearch/Resources/project-diagram.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/HelixTestLogSearch/Resources/project-diagram.png differ diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/ibcmerge.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/ibcmerge.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/ibcmerge.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/ibcmerge.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ -# IBCMerge during the CoreFX build - -This is an implementation plan to enable IBC training data merging during the CoreFX official build. - -The CoreFX official build fetches IBCMerge.exe using the internal tooling flow. See [fetch-internal-tooling.md](fetch-internal-tooling.md). - -Packages containing IBC data are restored using a project with an auto-updated dependency. Tentatively in https://github.com/dotnet/corefx/tree/master/external. - -If IBC merging is enabled by an msbuild property, the build uses `ibcmerge.exe` to merge IBC data into assemblies where applicable. A BuildTools target performs the merging between the `build` and `sign` sections of the build. The result is that signed official binaries contain merged IBC info. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5Cibcmerge.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5Cibcmerge.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5Cibcmerge.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/improve-arcade-reliability.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/improve-arcade-reliability.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/improve-arcade-reliability.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/improve-arcade-reliability.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,48 +0,0 @@ -# Improve Arcade Reliability - -The goal of this effort is to help us improve Arcade Validation by filling in its test gaps, while mitigating potential build breaks before the latest version of Arcade is pushed out to customers. - -## Step 1 - -We will reassess this step after two weeks to see if we're getting any value out of it. - -1. Create a scheduled pipeline similar to Arcade Validation today, with some alterations: - 1. If Arcade-Validation passes, create a branch of the `runtime` and `roslyn` repos using the last known good build within the last three days. (If there are no last known good builds in the last three days, we will skip building the latest Arcade against that repository.) - 2. Update those branches with the latest version of Arcade being validated. - 3. Build those branches and check for errors. - 1. Should any errors occur, Arcade SMEs should help to determine if the errors are related to Arcade or not. - 1. If the errors are related to Arcade, authors of commits included in the latest Arcade build since the last Arcade version release, must be contacted. - 2. Authors will be expected to fix whatever in their commits broke the other repos. - 3. Authors will be expected to add tests/validation to Arcade-Validation so that it will be caught in the future. - 2. If the errors are unrelated to Arcade we will send the Arcade build to the Latest channel to proceed with the normal dependency flow. - 4. The pipeline should run 3 times a week, scheduled during non-peak hours. - 5. Clean up branches of non Arcade-Validation repos used. - 6. Ensure that we can use existing telemetry to capture any data from the builds. Data from SME triage/investigation can be stored in an Excel spreadsheet (or another low cost document.) Data we want to capture: - 1. Repo and SHA branched from (since we are using the last known good build in the last three days). - 2. Result of the investigation: - 1. Passing (document that we ran Arcade through that branch and it was passing) - 2. Failure due to product (this will include the scenario if there are no last known good builds in the last three days, thus, we do not build Arcade against that repo) - 3. Failure due to Arcade/infra failure (this will result in any authors fixing the bug and contributing to the tests in Arcade Validation) - 4. Failure due to "Impedance Mismatch" (this is when there's too much churn) -2. After this process is set up, we will turn off automatic dependecy flow of Arcade. - -## Step 2 - -1. As test gaps are identified in Arcade Validation, we need to fill in those gaps with tests. This will be an ongoing process as test gaps are identified during the short term solution. -2. Once we are satisified with the robustness of Arcade Validation, we can re-enable automatic dependency flow of Arcade to the Latest channel. -3. Ensure that we have process/policy in place to promote adding validation to Arcade Validition when changes are made to Arcade. - -## Known Testing Gaps - -| Issue | Description | Root Cause | Resolution | -| ----- | ----------- | ---------- | ---------- | -| https://github.com/dotnet/arcade/issues/4660 | Roslyn signed builds failed to publish on 1/20/2020 to the package feeds in Dnceng due to authorization issues after taking an Arcade update. | A refactoring of powershell scripts done in 11/21/2019 made it so the script that enables the authentication for publishing and restore across AzDO account boundaries stopped running. | Made sure the script always runs during AzDO builds in https://github.com/dotnet/arcade/pull/4661 | -| https://github.com/dotnet/arcade/issues/4759 | Roslyn signed builds failed to queue due to a missing variable group after taking an Arcade update. | We refactored a YAML template so that the set of common variables that are required by post-build validation and publishing were shared across the stage instead of being referenced in each individual job. This caused the Validation stage to try and load a variable group that didn’t exist in DevDiv.
This break made it apparent that SDL validation was never set up to work for repos outside of dnceng as there was a variable group missing. | The variable group was created in DevDiv with the required variables and subsequent builds were queued successfully. | -| https://github.com/dotnet/arcade/issues/4748 | A Roslyn build was published to the “.NET Core SDK 3.1.2xx” channel in the Build Asset Registry, but no packages were actually published, so the Dependency flow PRs opened by this build were all failing to restore the packages. | The branch that produced the build was using a version of Arcade that didn’t have the publishing set up for that channel. | The branch was updated to use the latest arcade in the “.NET 3 Eng” channel, which brought in the correct publishing templates, and we’re working on adding a warning to builds that try to publish to channels and there’s no publishing implementation available in the YAML templates flowed to that branch. | -| https://github.com/dotnet/arcade/issues/4775
https://github.com/dotnet/arcade/issues/4728 | The darc version that gets installed by default from the darc-init scripts fails during any operation that uses the paged APIs. | The auto rest client generator generated an invalid client that would fail with a 404 for any APIs that are paged, such as update-dependencies, get-builds, or get-asset. | The generator, and the generated client Darc have already been patched, but we require a production deployment so that the darc-init scripts install a fixed version by default. | -| https://github.com/dotnet/arcade/issues/4860 | | Roslyn Updated the SDK version they use to build their compilers to 3.1, this change flowed to Arcade, and caused every Repo that wasn’t using a 3.1 SDK to break when compiling certain types of code that we don’t build in Arcade itself | | - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5Cimprove-arcade-reliability.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5Cimprove-arcade-reliability.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5Cimprove-arcade-reliability.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/MaestoTask_TestPlan.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/MaestoTask_TestPlan.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/MaestoTask_TestPlan.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/MaestoTask_TestPlan.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,140 +0,0 @@ -**Goals** - -Add test coverage for the Maestro.Task project with particular focus on the code that changed as part of the post-build signing changes. Each included method will have test coverage for the golden path(s) as well as interesting variations and missing/invalid input. I plan to start with the golden path and then use a combination of the VS code coverage tool and logic to identify the rest of the test cases for each method. - -Note that the specific test cases listed are an initial guess based on reading code that I am new to and are likely to change as my understanding of the code changes. - -**Methods to be covered (ones that were changed for post-sign build have a *** and will be the first to get coverage):** - - [ ] ***PushMetadataAsync (this will get tests late even enough it was changed because most of its logic is calling the other methods listed below, so I'm going to use Code Coverage to guide the test case creation) - - [ ] GetBuildDefaultChannelAsync - - [ ] Positive cases, validate that the correct subset of channels is returned - - [ ] Given all valid data (known good data set from a repo) - - [ ] Given GitHub source data - - [ ] Given AzDo source data - - [ ] Given both GitHub & AzDo source data - - [ ] Negative cases, validate that the returned data is empty or a meaningful exception is thrown - - [ ] Given that both GitHub & AzDo are empty - - [ ] Given empty/null values in other properties that are logged/checked - - [ ] GetBuildDependenciesAsync - - [ ] Positive cases, validate that the correct subset of BuildRefs is returned - - [ ] Given golden path (build is found and it has a set of dependencies that have all relevant values filled in) - - [ ] Given build that has product dependencies - - [ ] Given build that has tool dependencies - - [ ] Given a build with both product & tool dependencies - - [ ] Given a build with non-required fields empty (should have no impact on the returned list) - - [ ] Given one dependency with no builds found (GetBuildId returns null & this method logs then continues, return an empty list ) - - [ ] Given a build with no dependencies (return an empty list) - - [ ] Negative cases, expect a meaningful exception is thrown - - [ ] Given an empty RepoRoot - - [ ] Given that the RepoRoot is not defined in the input file - - [ ] Given an incorrect (invalid) RepoRoot - - [ ] GetBuildId - - [ ] Positive cases, validate that the correct buildId is returned and that the correct assets have been added to the assetCache - - [ ] Golden path (given assets have a matching commit, buildId has a value, and build has assets in it aren’t in the given list) - - [ ] Given assets are the only assets for build - - [ ] Negative, expect a meaningful expection is thrown - - [ ] Given assets don’t have a matching commit so no buildId is found (no exception, return null) - - [ ] Given assets are missing field values that are expected/used in logic - - [ ] Given null/empty arguments (if possible from caller) - - [ ] ***GetBuildManifestsMetadata - - [ ] Positive cases, validate contents of returned values (buildsManifestMetadata, signingInfo, manifestBuildData) - - [ ] Given a single manifest - - [ ] Given multiple manifests - - [ ] Given no manifests - - [ ] Negative cases, expect a meaningful exception is thrown - - [ ] Given a file that isn’t a manifest - - [ ] Given badly formatted XML - - [ ] Given an empty manifest file (valid XML formatting but with nothing in it) - - [ ] Given a set of manifests missing various pieces of expected data (examples below, not a complete matrix) - - [ ] Manifest without any assets listed - - [ ] Manifest that has an asset that contains a package with no version set - - [ ] Manifest that does not contain any Blobs - - [ ] Manifest with a blob that does not have a version set - - [ ] Manifest with a blob that does not have any assets - - [ ] Given two manifests that have different attributes (expect exception thrown in method) - - [ ] AddAsset - - [ ] Positive cases, validate that the asset has been added to to the asset list - - [ ] Given golden path with a combo of shipping and non-shipping assets - - [ ] Given empty string parameters - - [ ] Negative cases, expect a meaningful exception is thrown - - [ ] Given null parameters (if allowed by caller) - - [ ] MergeBuildManifests - - [ ] Positive cases, validate the contents of the merged manifest BuildData - - [ ] Given the golden path (two BuildData objects with compatible manifests in GitHub) - - [ ] Given the golden path with a mirrored repo - - [ ] Given three compatible BuildDatas - - [ ] Given compatible BuildDatas with null/empty assets - - [ ] Given compatible BuildDatas with existent but partially empty/invalid assets - - [ ] Negative cases, expect a meaningful exception is throwm - - [ ] Given two incompatible BuildDatas - - [ ] Given compatible BuildDatas with duplicated assets - - [ ] ***MergeSigningInfo - - [ ] Positive cases, validate the content of the merged SigningInformation - - [ ] Given two SigningInformation objects that are compatible and contain different information - - [ ] Given two duplicate SigningInformation objects - - [ ] Given two SigningInformation objects where one is missing some values - - [ ] Negative cases, expect a meaningful exception - - [ ] Given two SigningInformation objects that are not compatible (exception thrown by method) - - [ ] Given null/empty arguments (if possible from caller) - - [ ] LookingForMatchingGitHubRepository - - [ ] Positive cases, validate that the BuildData is updated with the correct infomration - - [ ] Given a BuildData that is based on GitHub (non-mirrored repo) - - [ ] Given a BuildData that is based on AzDo (mirrored repo) where GitHub is the current mirror - - [ ] Given a BuildData that is based on AzDo (mirrored repo) where AzDo is the current mirror - - [ ] Negative cases, expect a meaningful exception - - [ ] Given a BuildData with an invalid url for the AzureDevOpsRepository value - - [ ] Given null/empty arguments (if possible from caller) - - [ ] ***GetManifestAsAsset - - [ ] Positive cases, validate the contents of the AssetData and that it has been added to blobSet - - [ ] Given a list of AssetData with a location string and a manifest file that exists - - [ ] Given a list of AssetData with a location string and a manifest file that does not exist - - [ ] Given a golden path where the AssetVersion is set - - [ ] Given a golden path where the AssetVersion is not set, but there is a non-shipping asset with a version - - [ ] Given a golden path where the AssetVersion is not set and there is not a non-shipping asset with a version - - [ ] Given an empty list of AssetData (expect an empty list returned) - - [ ] Given a null location - - [ ] Negative cases, expect a meaningful exception - - [ ] Given other null/empty arguments (if allowed by caller) - - [ ] Given blobSet that already has a key with the same name as an asset (if possible to get into that state) - - [ ] ***CreateAndPushMergedManifest - - [ ] Positive cases, validate content of manifest file - - [ ] Given a list of compatible assets with SigningInformation & ManifestBuildData within expected values - - [ ] Given assets that are blobSet - - [ ] Given assets that are not in blobSet - - [ ] Given assets where some are in blobSet and some are not - - [ ] Given assets that are non-shipping - - [ ] Given assets that are shipping - - [ ] Given other empty/null arguments (as allowed by caller) - - [ ] Given a mergedManifestPath that does not exist - - [ ] Given a mergedManifestPath that already contains a file with the same name - - [ ] Negative cases, expect a meaningful exception - - [ ] Given null SigningInformation - - [ ] Given SigningInfo that has some null/empty values - - [ ] ***SigningInfoToXml (this is going to be a fine line between testing our logic and testing XDocument, so these test cases are very likely to evolve as I write them) - - [ ] Positive cases, validate content of the returned XElement - - [ ] Given a SigningInformation with a single one of each of the types parsed and all values filled in - - [ ] Given a SigningInformation with multiple of the types parsed, all of which contain the expected values - - [ ] Negative cases, expect a meaningful exception - - [ ] Given a SigningInformation missing various top level values that are assumed present (if allowed from caller) - - [ ] Given a SigningInformation with nested values missing - - [ ] Scenario Tests - - [ ] Positive cases, validate that the BAR database has been updated with the correct information and that the new build exists - - [ ] All values and input are valid throughout the pipeline - - [ ] Negative cases, a user friendly error message is returned - - [ ] RepoRoot is null or empty - - [ ] AssetVersion is null or empty - -**Non-Goals** -Test coverage for any code outside of the Maestro.task project. - -**Methods to be excluded:** - -- “GetXYZ” methods where it only returns a string from the environment or calls an outside function without applying interesting logic. - -**Risk** -This is fairly low risk, since it’s going to be based on the other existing test projects. There are some changes required in the product code to allow for DI and mocking attachment points, that’s the biggest risk in this set of changes. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CMaestoTask_TestPlan.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CMaestoTask_TestPlan.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CMaestoTask_TestPlan.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/OneLocBuild/one-pager.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/OneLocBuild/one-pager.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/OneLocBuild/one-pager.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/OneLocBuild/one-pager.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,60 +0,0 @@ -# Generating the Localization Index File (`LocProject.json`) for the New Localization System (OneLocBuild) - -## Project Summary -For a variety of reasons, the localization workflow is changing and we need to [migrate to the new loc system](https://github.com/dotnet/arcade/issues/6842). -This system is essentially an Azure DevOps task([OneLocBuild](https://ceapex.visualstudio.com/CEINTL/_wiki/wikis/CEINTL.wiki/107/Localization-with-OneLocBuild-Task)) -that we run in each repo's build pipeline to gather up our English resource files, send them off to the localization system, and receive -localized resource files back. Because this is common infrastructure that will need to be implemented across all of our customer repos, -it makes sense to implement it in Arcade. - -More information on migration can be found [here](https://ceapex.visualstudio.com/CEINTL/_wiki/wikis/CEINTL.wiki/1481/Migrating-out-of-SimpleLoc?anchor=ado-pipeline-creation-for-projects-hosted-in-github). - -A major component of the new localization system is an index file called `LocProject.json`. In the linked documentation above, this file is -checked into the repository, which would require every single repo to maintain a list of all of their resource files that need to be localized -manually and make sure it stays in sync with their changes. This is a non-ideal solution. Instead, we hope to generate the `LocProject.json` file -at build time prior to running the OneLocBuild task. - -## Goals -The primary goal of the project is to generate the `LocProject.json` file at build time so that we can automate the localization process -as much as possible with minimal to no intervention from customer repos. - -## Stakeholders -The primary stakeholders for this project are the .NET Core Engineering team (who will maintain this process on -behalf of our customer repos) and the localization team. The other stakeholders include all of the customer repos who may have -to maintain some new files for localization on their end if we aren't able to completely automate the work within `eng/common`. - -## Risk -The most significant risk facing this project is any information in the index file that needs to be manually tweaked. As an example, -[@RussKie](https://github.com/RussKie) created a [first pass attempt](https://github.com/dotnet/arcade/issues/6842#issuecomment-771963490) -at this and found that he still had to manually remove some .resx files from the `LocProject.json` file after generation. We could -create something like an exclusions list or other file that is *more* static than the `LocProject.json` would be to take much -of the burden off of customer repos if we can't find a way to be smart with our file inclusion. - -To mitigate this risk, we will work with the localization team to see if we can replicate the logic they were using previously when they were -automatically scanning our repos and loop in the customer repos to make sure the localization pipelines are working properly for them. - -A second risk facing us is the need to backport this to servicing branches of Arcade. At this time it is unknown if there will be significant -challenges to this separate from the ones we currently face in master. We will work in tandem with [@mmitche](http://github.com/mmitche) -to make sure that this is mitigated as much as possible. - -Finally, the hard deadline of March 31, 2021, which is when the old localization system will be turned off is a risk. While we will likely -be able to accomplish the majority of the work by this point, the unknowns of the servicing branches, in particular, are worrisome. We will work -with the localization team to put in place a temporary manual process they recommended if this date slips for any of our branches. - -## Serviceability -Two PATs are required by the OneLocBuild task: a GitHub PAT and an AzDO PAT for the [ceapex organization](https://dev.azure.com/ceapex). -The latter will have to be created and maintained. - -There will be tests for the `LocProject.json` generation script and any other scripts that are created to ensure they are generating files -correctly. - -### Rollout and Deployment -This project will be tested thoroughly in customer repos before we check it into Arcade. Once we do check it into Arcade, it will simply be rolled out -as a part of Arcade. - -## FR Handoff -Most likely, we will only need to write a single document on the results of this project to facilitate FR handoff. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5COneLocBuild%5Cone-pager.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5COneLocBuild%5Cone-pager.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5COneLocBuild%5Cone-pager.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/one-pager-template.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/one-pager-template.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/one-pager-template.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/one-pager-template.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,71 +0,0 @@ -# Epic Template - One-Pagers Guidelines - -## Goal and Motivation - -The information included within our epics are high level business objectives and does not always leave much room for practical information. Sometime the v-team is able to capture all the information listed within outlined below in the epic. If that is the case, there is no need for an additional document. - -In most cases, however, v-teams need a place where they can capture additional information that helps them "think about" how they are going to implement a given feature. - -The goal of the one-pager is to bring clarity to how the v-team is going to implement and support specific aspects of the business goals defined in the epic. - -The document below is meant to be a guideline on what the v-team should be thinking about when defining the feature they are working on. It is up to you what you include in your one pager. - -## One-Pager Guidelines - -In this section you will find the areas that you should consider including in your one-pager. - -### Stakeholders - -Who is this work for (i.e. stakeholder and those that should "sign-off" on your POC) and what are the problem(s) they asking us to solve? - -### Proof of Concept (POC) - -An effective proof of concept proves the goal of a proposed project is viable, and will be successful. The value of a POC is it can help the v-team identify gaps in processes that might interfere with success. - -A POC can help -- Elicits feedback from everyone involved in a project, including those who might not have otherwise contributed, thereby mitigating unforeseen risk. -- Creates a test project to evaluate before work begins on an actual project. -- Verifies that concepts and theories applied to a project will have a real-world application. -- Helps us to prove our assumptions (for example, if certain functionality, like using a service account to post comments from GitHub to Teams in a service, is possible) before committing to completing the work in a given timeframe. -- Helps us to adjust our expectations about how much work a feature might take to complete depending on the challenges we run into that we didn't originally consider in our assumptions. -- Can have more than one POC, if necessary, for a project. - - -### Risk - -- Will the new implementation of any existing functionality cause breaking changes for existing consumers? -- What are your assumptions? -- What are your unknowns? -- What dependencies will this epic/feature(s) have? - - Are the dependencies currently in a state that the functionality in the epic can consume them now, or will they need to be updated? -- Is there a goal to have this work completed by, and what is the risk of not hitting that date? (e.g. missed OKRs, increased pain-points for consumers, functionality is required for the next product release, et cetera) -- Does anything the new feature depend on consume a limited/throttled API resource? -- Have you estimated what maximum usage is? -- Are you utilizing any response data that allows intelligent back-off from the service? -- What is the plan for getting more capacity if the feature both must exist and needs more capacity than available? - -### Usage Telemetry - -- How are we measuring the “usefulness” to the stakeholders of the business objectives? -- How are we tracking the usage of this new feature? - -## Service-ability of Feature - -Changes that we implement often require addition maintenance to support them long term. The FR group has been set up to handle this work but it is up to the v-team to make sure FR is successful in servicing the changes made within your epic long term. Please see the [Servicing Guidelines](https://github.com/dotnet/arcade/blob/main/Documentation/Project-Docs/Servicing%20Guidelines.md) Document for what you should be thinking about during your feature creation to help the team be able to easily service your feature long term. - -## House Keeping - -In order to align with Epic Content Guidance, one-pagers should be stored in a central location. -- The folder to store your One-Pager can be found in the [Documentation Folder](https://github.com/dotnet/arcade/tree/main/Documentation/TeamProcess/One-Pagers) -- The name the one-pager should contain the name of the epic and the epic issue number (for easy reference). - - Example: Coordinate migration from "master" to "main" in all dotnet org repos - core-eng10412.md. -- Use the PR process to document the discussion around the content of the one-pager. - -Guidance for Epics can be found at [Guidelines for Epics](https://dev.azure.com/dnceng/internal/_wiki/wikis/DNCEng%20Services%20Wiki/552/Guidelines-for-Epics) wiki. - -After all discussions have been resolved, the resulting one-pager document should be signed-off (this does not need to be a formal process) by stakeholders (e.g. v-team members, epic owners, et cetera) and then linked to the associated epic's GitHub issue for discover-ability. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5Cone-pager-template.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5Cone-pager-template.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5Cone-pager-template.md) - Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/OS Onboarding/Images/DevWorkFlow.JPG and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/OS Onboarding/Images/DevWorkFlow.JPG differ diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/OS Onboarding/Requirements.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/OS Onboarding/Requirements.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/OS Onboarding/Requirements.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/OS Onboarding/Requirements.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,78 +0,0 @@ -# Overview -The requirement for Platform/OS onboarding is that it should be relatively painless to add/update/delete Queues/Scalesets and doable by all customers. - -# Requirements -- All Exisiting queue/scaleset combos are described in one or more yaml files in a VSTS/Github Repo which looks something like this. -``` -- &Windows10 - Name: Windows.10 - AzureImage: - Name: Windows - Version: 10 - Artifacts: - - vs_15_08 - - helix_runtime - MaximumScale: 50 - Owner: abc@ - -- <<: *Windows10 - Name: Windows.10.Open - Public: true - MaximumScale: 20 - Owner: abc@ - -- Name: Windows.7.Amd64 - Public: true - MaximumScale: 20 - location: westus - tags: { - "QueueId": "Windows.7.Amd64", - "ResourceGroupName": "Windows.7.Amd64.WestUS", - "WorkspacePath": "D:\\\\j", - "IsAvailable": "true", - "IsInternalOnly": "true", - "UserList": "all", - "OperatingSystemGroup": "windows" - } - scaling rule: - Owner: abc@ - -- Name: Windows.7.Client - BaseImage: http://dotnet-eng-images.storage.azure.net/base-images/Windows.7.Client/15.6.750.vhd - Artifacts: - - vs_15_08 - - helix_runtime - MaximumScale: 3000 - Owner: abc@ - -- Name: OSX.1012 - Unmanaged: true - Owner: abc@ - -- Name: TOF.External - Unmonitored: true - Owner: abc@ -``` -- User makes a PR to the repo to add/edit/delete scalesets in one of the yaml files. -- Validation Service in the Repo runs sanity checks on like “Does that image exist” and “Are those artifacts known artifacts”, if possible. -- If Validation succeeds, send a PR to Image Creation Factory with specific parameters (TBD) to create Image /Artifcacts . -- Wait for the image to be created, Image Factory notifies via a webhook with a status ("image complete"/"failed to create an image" etc.) -- If the image is created, validate that the image works by creating a scaleset and deploying to INT/staging with one machine and test with a sample job. If artifacts are requested, quick validation per artifact to make sure the artifact is operating as expected (e.g. if someone wanted VS, make sure a “msbuild test.proj” does the right stuff, if helix is request, make sure it reads a queued item and processes a job) -- If anything failed, mark the PR as failed -- Maintain an image mapping yaml, which contains a mapping against commit# and the Image/Artifact created by the Image Creation Factory. -- When the User-initated PR or PR to update image mapping yaml merges, initiate CI build/release that - - Creates any required queue - - Transforms every defined scaleset/queue combo into an Azure ARM template and pushes all those, which will update any existing scale set, and create new ones as necessary. This might involve fetching some temporary secrets to initialize things - - Delete any scale/set queue that is defined but not in this repository anymore (so we can decommission things) -- To handle updating existing scale sets (some VMs running “newer” images than others), we need to augment the VM cleanup tool to detect when there are “older” VM’s in the scale set that contains newer ones and mark them as “unhealthy” so the get deleted, and newer images take their place -- Cleanup service runs periodically every n days to clean up outdated VMs/scalesets, update Image Mapping yaml accordingly. -- Add Helix as an Artifact to ImageFactory - -# Dev Work Flow -![](./Images/DevWorkFlow.JPG?raw=true) - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5COS%20Onboarding%5CRequirements.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5COS%20Onboarding%5CRequirements.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5COS%20Onboarding%5CRequirements.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/RepositoriesInTheirOwnSubscription.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/RepositoriesInTheirOwnSubscription.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/RepositoriesInTheirOwnSubscription.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/RepositoriesInTheirOwnSubscription.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,58 +0,0 @@ -# Repositories working on their own azure subscription - -In order to meet our SLA and to limit our azure spending, there are repositories that need to run on their own subscription1. - -There are some guidelines to decide when a repository needs to be transferred to their own subscription. This is going to be shared later. - -## How do I transfer a repository to its own subscription? - -To transfer a repository to their own subscription we will go through the following process: - -1. Work with DDFun2 to create a new Azure Subscription. You will need to provide the number of cores that you need, for which [region](https://docs.microsoft.com/en-us/azure/virtual-machines/regions) and which [VM type](https://docs.microsoft.com/en-us/azure/virtual-machines/sizes) you need. The process of creating the new subscription takes around 1-2 weeks. - -2. Identify the Helix queues this repository needs. A repository usually only uses a subset of the available Helix queues, so it is important to identify the ones that will be duplicated. - -3. Create the description of the subscription on subscriptions.yaml: - - Subscription - Name of the subscription. - - TeamName - Name that is going to identify the queues for this subscription. - - Repositories - The repository/repositories you want to migrate. - -4. Add the list of queues to the corresponding yaml definition files. The name should be of the form `.`. The **QueueName** is the name of the queue you are duplicating and the **TeamName** is the one defined in the step above for that subscription. - -5. The last step is to let the AutoScaler and the Core Rebalancer know about the new subscription. This should be done in [*AutoScaleQuotaConfigProd.json*](https://dev.azure.com/dnceng/internal/_git/dotnet-helix-machines?path=%2Fsrc%2FServiceFabric%2FProcessAutoScaleService%2FConfigs%2FAutoScaleQuotaConfigProd.json&version=GBmaster&_a=contents) where the subscriptionId and the quota for the subscription needs to be added. -One important thing to keep in mind is that in most cases the cores added to the new subscription account for our overall core consumption in other words the cores for the new subscription needs to be subtracted from another subscription most likely HelixProd. - -## How this works -![](./assets/RedirectJobsWorkflow.png) - -1. The user sends the Job to the *Queue* and as part of the job information the user includes the *Repository*. - -2. The Helix JobController gets a list of all the queues under that repository and if the ```*Queue*``` exists as part of that list a new queue is assigned for that job, the new queue is going to be ```*Queue.TeamName*```. - -If there is not any match the queue doesn't change, which in most case means that this is going to be processed by HelixProd. This could happen for queues that the repository doesn't usage on a significant way, but if this start happening in queues in which the impact is notable for HelixProd, we should create a specific queue for the repository. - -Internally *Queue* and *Queue.TeamName* lives on different subscription so as soon as we send the job to another queue, we are sending that job to another subscription. - -It's important to keep in mind that we are maintaining all these new queues and these queues works on the same way that the rest. This means that as soon as a job has been redirected this jobs is going to wait for a machine on that queue (until the queue scales up), retry work items on that queue if the work item fails and if there is any problem we need to handle that problem on the *Queue.TeamName* queue. - -# How many cores should I assign to the new subscription - -Our service is authorized to consume a specific number of cores so even after splitting the queues to a new subscription we should stay under that authorized limit. For example, HelixProd can have 6500 cores authorized and we plan to have a new subscription (splitting it from HelixProd) we could end having HelixProd with 4500 and the new subscriptions with 2000, having in total the 6500 cores. - -After identifying which are the queues you are going to duplicate you need to know the number of cores that are used by that repository, for that you can do the following steps, for every queue: - -1. Get the `total number of cores` consumed by that queue during a timespan. -2. In the same timespan, identify what `percentage` of the jobs that are sent to that queue belongs to the repositories that you are creating a new subscription for. -3. Compute the number of cores that the repository is consuming in that queue, based on the `percentage` and the `total number of cores` (`total number of cores` * `percentage`) - -Once the total cores needed per queue is calculated, sum up the total cores for the queues in question to get an idea on how many cores is needed for that repository. **Suggestion:** Give 5% more to the total cores that you calculated. - - -# -###### 1Unit of Azure Billing and organization -###### 2 Reach out to @ilyas1974 - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CRepositoriesInTheirOwnSubscription.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CRepositoriesInTheirOwnSubscription.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CRepositoriesInTheirOwnSubscription.md) - diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/SBOM Generation/one-pager.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/SBOM Generation/one-pager.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/SBOM Generation/one-pager.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/SBOM Generation/one-pager.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,44 +0,0 @@ -## Generating the Software Bill of Material (SBOM) - -## SBOM -The Executive Order(EO) and The National Telecommunications & Information Administration (NTIA) report defines an SBOM as a formal record containing the details and supply chain relationships of various components used in building software. On May 12, 2021, the U.S. Presidential EO, section 4(e)(vii) is requiring all software sold to the federal government to provide a Software Bill of Material (SBOM). - -SBOM is usually a single file (such as .json) that captures this information about the software from the build. Microsoft has decided to use Software Package Data Exchange (SPDX) as its SBOM format of choice. All software produced from Microsoft will have an SPDX SBOM. - -SBOMs provide two core benefits: - -i) Software Transparency - this is a small step towards increasing trust as the SBOM describes the "ingredients" of the software and their relationships. This also enables external consumers of SBOMs to do vulnerability lookups on the open source software embedded within. -ii) File checksums for integrity verification purposes - -## Goals -Primary goal is to generate SBOM for all the software produced by .Net. Here we are focusing on the following areas: - -i) Staging pipeline -ii) Arcade and all the repos that use arcade eg: Runtime, aspnetcore etc. -iii) Repos that are not on-boarded to arcade eg: arcade-services, OSOB, helix etc. - -## Stakeholders -- .NET Core Engineering -- .NET Core Engingeering Partners -- Microsoft - -## Unknowns -There are 2 ways to generate SBOM -1) Azure Task - Helps with generation of SBOM and uploads it to db -2) Executable - Creates the manifest but uploading is TBD - -## Rollout and Deployment -- Firstly we will be generating SBOM for staging pipeline. Here we already have a place where we upload all the signed assets, so we will need to add a azure task to generate and upload the SBOM. After generating SBOM, we will need to get a one time manual sign off from partners to see if the generated SBOM is valid and contains all the 'expected' items. -- Then focus on our Engineering systems - In Arcade (main branch) we are planning to use the executable to generate SBOM. Here we will validate if SBOM is generated correctly. In Arcade we will add a feature flag for SBOM generation. We will initially turn on this feature for a few repos and see if SBOM is getting generated correctly, then roll out for all the other repos. This gives repo owners the ability to opt-out of the feature incase of failure, while we investigate. -- Backport SBOM generation changes to release/6.0 branch. -- The repos that use arcade may have multiple places where will have to generate SBOM. We will need to generate SBOM for all the repos that use arcade.There might be multiple SBOM in this scenario. Then we need to get sign off from the repo owners to validate SBOM. -- Lastly, we will have to work on repos that are not on-boarded to arcade like arcade-service, helix, OSOB. - -## FR handoff -- Will document SBOM generation in arcade and how repo owners can on-board. -- Will document any failures as I encounter. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CSBOM%20Generation%5Cone-pager.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CSBOM%20Generation%5Cone-pager.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CSBOM%20Generation%5Cone-pager.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/SDL/arcade-services-fall-2020-one-pager.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/SDL/arcade-services-fall-2020-one-pager.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/SDL/arcade-services-fall-2020-one-pager.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/SDL/arcade-services-fall-2020-one-pager.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -### Stakeholders - -Stakeholders are: Chris Bohm as SDL owner but also whole .NET Engineering Services team as this work relates to every part of our codebase. - -### Risk - -Biggest potential risks are: -- Huge surface area to cover - Arcade Services contains many different services, tools and libraries. They all have to be assessed separately. This will also require a lot of investigative work especially in less known parts of our infrastructure. -- Mission Control service - it was not deployed since a long time and we have to make it compilable and deployable again to fix reported SDL vulnerabilities -- Risk of big changes to our services for compliance reasons that will either consume a lot of time or require us to do big breaking changes to our services. -- We have 60 days to fix the found vulnerabilities so there is also a risk of running out of time. Not meeting this deadline could potentially cause some legal/compliance problems for our team. - -### Serviceability - -This epic will change only those parts that are found to be non-compliant. We don't expect many changes to testing, deployment and servicability of our components. In cases where it will be needed we will try to document the process inside relevant services. We will also prepare the documentation summarizing the work done and tips for future SDL rounds. - -#### Rollout and Deployment - -This epic does not introduce any new componentss so there won't be any major changes to rollout and deployments. The only exceptions are: -- Mission Control - it haven't been deployed since long time so we will need to get the deployment scripts to working state again and deploy it. -- Grafana - we need changes to deployment scripts to be able to deploy specific version of Grafana instead of the latest one found in package repository. This is needed so that version of Grafana in Component Governance matches the actual instance. - -### Usage Telemetry - -There is no telemetry to track in this epic. The only metric is that we need to close all work items in SDL assessments. - -### Monitoring - -No new monitoring is needed. - -### FR Hand off - -There is no FR hand off required but we will prepare documentaton summarizing what has been done and put it in dotnet/core-eng wiki. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CSDL%5Carcade-services-fall-2020-one-pager.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CSDL%5Carcade-services-fall-2020-one-pager.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CSDL%5Carcade-services-fall-2020-one-pager.md) - diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Secret Management/one-pager.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Secret Management/one-pager.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Secret Management/one-pager.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Secret Management/one-pager.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# Secret Management -We need a secret management system which allows us to audit and monitor secrets, rotate them in an automated fashion (as much as possible), and manage appropriate / inappropriate secret usage. More specific (and additional requirements) are available [below](#requirements). - -## Requirements -Requirements [gist](https://gist.github.com/chcosta/51af24ab8a1cfd303a50d0aa7332e7f0) - -## Stakeholders -- The First Responder team -- All of .NET Core Engineering - -## Risks -- Another service that we have to manage and monitor -- Adds more policy around secret usage in our services - -### Unknowns -- Can we monitor all key vault secret accesses? Azure has access logs for this that log requesting IP. Is that enough? -- There are categories of secrets which don't involve a service accessing key vault for the value (things like account passwords and otp codes), can we monitor those without introducing another layer? If not, is that ok? These should be monitored and managed in a key vault just accessed manually and/or with a tool when a person needs them. -- Can we use a third party tool for secret management? None of them look promising. https://github.com/microsoft/AuthJanitor has a disclamier in their readme about it not being ready for prime time, and it requires deploying a website that requires SDL stuff. - -### Proof of Concepts -- https://github.com/microsoft/AuthJanitor exists. We very much don't want a website, but can we use some of this. -- https://www.vaultproject.io/use-cases/secrets-management looks cool but costs money. -- Azure has a sample https://github.com/Azure-Samples/serverless-keyvault-secret-rotation-handling but that is just boiler plate that we might be able to take something from. - -## Serviceability -- Tests for rotation of all secrets that can be rotated -- Management system runs for PRs to validate configuration, and changes are validated in staging before deployment -- The tool will not accept customer input, so doesn't affect SDL or threat model. All authentication will be handled by azure cli, so arbitrary people can't mess with secrets they don't already have access to. - -## Rollout and Deployment -- We need to deprecate the existing "secret notifier" -- This will be deployed with the existing services - -## Usage Telemetry -- Usage will be tracked in application insights - -## Monitoring -- Grafana alerts and build results - -## FR Hand off -- Will create documentation about - - How to use the tool - - What to do when it fails the build - - What to do when a secret gets leaked or expires - - How to diagnose the tool when alerts get triggered - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CSecret%20Management%5Cone-pager.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CSecret%20Management%5Cone-pager.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CSecret%20Management%5Cone-pager.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/security-builds.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/security-builds.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/security-builds.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/security-builds.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,169 +0,0 @@ -# Security Builds for .NET Core - -This document describes security builds of .NET Core. - -- [How to setup a security build](#how-to-setup-a-security-build) -- [How to kickoff a security build](#how-to-kickoff-a-security-build) -- [How to get values for queue variables](#how-to-get-values-for-queue-variables) -- [How to access and resolve security issues](#how-to-access-and-resolve-security-issues) - - -## How to setup a security build - -Security Development Lifecycle ([SDL](http://sdl/)) specifies the minimum security requirements that must be satisfied before making a Microsoft software or service available to customers. To help product teams fulfill the security requirements, SDL team provides a few tools and services, in addition to detailed guidance and a dedicated support team. Some these tools and services are available as a VSTS extension called Secure Development Tools ([SDT](https://www.1eswiki.com/wiki/Secure_Development_Tools_VSTS_Extension)), which is a collection of build tasks. These build tasks can be added to a VSTS build definition. - -Trust Services Automation ([TSA](http://sql/wiki/Trust_Services_Automation_%28TSA%29)) is a service that analyzes the logs produced for security tools, identifies regressions, creates workitems to track the regressions, and generates a detailed report. One of the tasks in SDT extension is to collect logs from security tools and upload them for processing at TSA. This allows product teams to setup a VSTS build definition that acquire the latest version of security tools, run the tools against the product, gather and analyze logs, detect regressions, and prepare reports. - -Security build for .NET Core is a VSTS build definition that uses SDT extension. A security build does not involve building the product from source. This build operates on build artifacts of an official build. The approach for security build can be summarized as follows. - - 1. Download the packages, using `sync` command, for the specified official build Id - 2. Extract assemblies and symbols from the packages - 3. Run security tasks that scan assemblies. Use APIScan and BinSkim tasks from SDT extension. - 4. Get the sources at the SHA specified in `version.txt`, which is obtained when packages are extracted at step #2 - 5. Run security tasks that scan source code. Use CredScan and PoliCheck tasks from SDT extension. - 6. Gather logs and upload to TSA. Use publish task in SDT extension. - -SDT extension currently support 4 tools that are applicable to .NET Core. A short description of each tool is shown in the table below. - -|Tool|Description| -|:---|:----------| -| BinSkim | Validates compiler/linker settings and other security-relevant binary characteristics.| -| APIScan | Determines whether or not the software complies with the API Usage Standard of the Interoperability Policy.| -| CredScan | Index and scan for credentials or other sensitive content.| -| PoliCheck | Scan code, code comments, and content for words that may be sensitive for legal, cultural, or geopolitical reasons.| - - -.NET Core security build definitions and link to the report is listed in the table below. - - -|Build Definition|TSA Report| -|:---------------|:---------| -| [CoreFx](https://devdiv.visualstudio.com/DevDiv/_build/index?context=allDefinitions&path=%5CDotNet%5CSecurity&definitionId=6552&_a=completed) | [CoreFx-master](http://aztsa/api/Result/CodeBase/DotNet-CoreFx-Trusted_master/Summary) | -| [CoreCLR](https://devdiv.visualstudio.com/DevDiv/_build/index?context=allDefinitions&path=%5CDotNet%5CSecurity&definitionId=6598&_a=completed) | [CoreCLR-master](http://aztsa/api/Result/CodeBase/DotNet-CoreCLR-Trusted_master/Summary) | -| [Core-Setup](https://devdiv.visualstudio.com/DevDiv/_build/index?context=allDefinitions&path=%5CDotNet%5CSecurity&definitionId=6658&_a=completed) | [Core-Setup-master](http://aztsa/api/Result/CodeBase/DotNet-Core-Setup-Trusted_master/Summary) | -| [CLI](https://devdiv.visualstudio.com/DevDiv/_build/index?context=allDefinitions&path=%5CDotNet%5CSecurity&definitionId=6698&_a=completed) | [CLI-master](http://aztsa/api/Result/CodeBase/DotNet-CLI-Trusted_master/Summary) | - -In the current setup, a security build is triggered manually. Official Id and corresponding Azure container name needs to be provided at the time of queuing the build. In near future, Maestro will be extend to determine the Official Id and container name, and trigger a security build automatically. - -TSA is configured to send an email report for each scan or security build to [dncsec](dncsec@microsoft.com) that include .NET Core repository owners responsible for security issues. Repository owners should focus on new issues and regressions highlighted in the report, and take necessary action to resolve those issues. - -## How to kickoff a security build - -Kickoff of a security build is as simple as queuing a VSTS build definition. While queuing, values for four input variables need to be provided. These variables are as follows: - - - *PB_BuildNumber* - official build Id of the repository. - - *PB_CloudDropContainer* - name of the Azure container from where the packages published from the official build (*PB_BuildNumber*) can be downloaded. - - *CodeBase* - TSA codebase that corresponds to the branch. For example, `master` or `2.0.0`. - - *NotificationAlias* - A comma separated email Ids where the TSA report should be sent. - -For example, a recent build Id of CoreCLR `2.0.0` branch is `20170621-01`. Packages produced from this build were published to Azure container named `coreclr-preview3-20170621-01` . To launch a security build that will scan the assemblies and source code from that official build, perform the following steps: - - 1. Navigate to CoreCLR security build [definition](https://devdiv.visualstudio.com/DevDiv/_build/index?context=allDefinitions&path=%5CDotNet%5CSecurity&definitionId=6598&_a=completed) - 2. Click "Queue new build" - 3. Enter the variable values: - - *PB_BuildNumber* = `20170621-01` - - *PB_CloudDropContainer* = `coreclr-preview3-20170621-01` - - *CodeBase* = `2.0.0` - - *NotificationAlias* = `dncsec@microsoft.com,joc@microsoft.com` - - Refer to the screenshot below. See [how to get values for queue variables](#how-to-get-values-for-queue-variables) - 4. Click OK to start the build - ----------- -![QueueSecurityBuild.](./assets/QueueSecurityBuild.png?raw=true) - ----------- - -#### Core-Setup - -Core-Setup requires an additional queue variable called `PB_BlobName`, which is the name of the Azure Storage blob that contains the packages produced from the official build under test. This blob is under the default container named `dotnet`. - ----------- -![QueueCoreSetup.](./assets/QueueCoreSetup.png?raw=true) - ----------- - -#### CLI - -CLI builds are fully automated. This means no variable needs to be set at the time of queuing. -A build is triggerred everyday around midnight. Build downloads the latest packages (zip) from Azure Storage corresponding to the branch. For example, latest packages of `master` branch are downloaded from (https://dotnetcli.blob.core.windows.net/dotnet/Sdk/master). SHA and build number are read from `latest.version` file (https://dotnetcli.blob.core.windows.net/dotnet/Sdk/master/latest.version). - - -As described in the earlier section, when the build finishes successfully, an email report of the security build is sent to listed email Ids. The same report can be viewed online. For example, report for CoreCLR `2.0.0` will be at TSA [website](http://aztsa/api/Result/CodeBase/DotNet-CoreCLR-Trusted_2.0.0/Summary) - - -### How to get values for queue variables - -Team dashboard [MC](https://mc.dot.net) is the place to begin when looking for details about .NET Core builds. In the dashboard, navigate to .NET Core release branch such as [2.0.0](https://mc.dot.net/#/product/netcore/200) to get the summary of most recent builds. Described below is how to get the values for queue variables for each .NET Core repository's security build. - -*PB_BuildNumber* is the official build number, and is a required variable in security build of all four repositories. To determine this build number for a repository, navigate to the dashboard, identify the most recent build under the corresponding repository. Build number is usually in a year-month-day format. For example, `20170622.01`. Replace the dot with a hyphen. In this example, *PB_BuildNumber* is `20170622-01`. - -*PB_CloudDropContainer* is the name of the container where the packages produced from *PB_BuildNumber* build are stored. To get this container name, in the dashboard, click on the build number link or button. In the details, click the URL against `buildUri` to navigate to VSTS build. Navigate to the log for `PipeBuild.exe` task in this VSTS build, and locate the container name as described below. - - -#### CoreFx - -In case of CoreFx, container name is the value against `PB_Label`. Shown below is a portion of `PipeBuild.exe` task log showing the container name. - ->OfficialBuildId=20170622-01 PB_SignType=real PB_Label=**corefx-preview1-20170622-01** SourceVersion... - - -#### CoreCLR - -In case of CoreCLR, container name is `Label`. Shown below is a portion of `PipeBuild.exe` task log showing the container name. - ->OfficialBuildId=20170622-01 SignType=real Label=**coreclr-preview3-20170622-01** SourceVersion... - -#### Core-Setup - -In Core-Setup, the default container name (*PB_CloudDropContainer*) is `dotnet`. An additional variable named `PB_BlobName` is required for security build of Core-Setup. To locate this value, open `PipeBuild.exe` task log, search for the build leg named `Core-Setup-Publish`, and click the URL against this to navigate to the build leg. Example fragment from the log is shown below. - - >Core-Setup-Publish - https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_build?_a=summary&buildId=820812... - -In the build leg, locate text similar to the fragment shown below. - ->Downloading **Runtime/2.0.0-preview3-25422-01**... - - -`Runtime/2.0.0-preview3-25422-01` is the value for `PB_BlobName`. - ----------- - - -## How to access and resolve security issues - -For each successful build, TSA analyzes the logs from the build, and create issues, which are VSTS workitems. Query to the workitems will be in the email report sent to `dncsec`. As mentioned earlier, the report can be accessed at TSA reports, whose URL is in the format - `http://aztsa/api/Result/CodeBase//Summary`. For example, CoreFx master is at `http://aztsa/api/Result/CodeBase/DotNet-CoreFx-Trusted_master/Summary` - -Repository owner is responsible to triage, and drive towards resolving all security issues logged against the codebase. There are certain cases where an issue cannot be fixed, a few of them are summarized below. - -#### Case #1: External - -Say an issue is with an assembly that is not owned or built by the repository, then resolve the issue by setting the following attribute-value in the workitem. - -|Attribute|Value| -|:--------|:----| -| State|Done | -| Reason | Work Finished | -| Status | Resolved | -| Resolution | Will not Fix | - -TSA will stop reporting such issues in future builds. - -#### Case #2: Configuration - -Say there was a configuration error while launching the build. For example, the branch name was set to `2.0.0` instead of `master`. This will pollute TSA codebase with issues from other branch. So, to cleanup the codebase, resolve such configuration issues by setting the following attribute-value in the workitem. - -|Attribute|Value| -|:--------|:----| -| State|Done | -| Reason | Work Finished | -| Status | Resolved | -| Resolution | Configuration/Environment | - - -For any questions about security builds, please contact [dncsec](dncsec@microsoft.com). - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5Csecurity-builds.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5Csecurity-builds.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5Csecurity-builds.md) - diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Servicing Guidelines.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Servicing Guidelines.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Servicing Guidelines.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Servicing Guidelines.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -# Placeholder for Servicing Guidelines - -From One-Pager Guidelines - -### Serviceability - -- How will the components that make up this epic be tested? -- How will we have confidence in the deployments/shipping of the components of this epic? -- Identifying secrets (e.g. PATs, certificates, et cetera) that will be used (new ones to be created; existing ones to be used). - - Instructions for rotating secret (if the secret is new) -- Does this change any existing SDL threat or data privacy models? (models can be found in [sharepoint](https://microsoft.sharepoint.com/teams/netfx/engineering/Shared%20Documents/Forms/AllItems.aspx?FolderCTID=0x01200053A84D1D9752264EB84A423D43EE2F05&viewid=6e9ff2b3%2D49b8%2D468b%2Db0d3%2Db1652e0bbdd3&id=%2Fteams%2Fnetfx%2Fengineering%2FShared%20Documents%2FSecurity%20Docs) folder) -- Does this require a new SDL threat or data privacy models? -- Steps for setting up repro/test/dev environments? - -#### Rollout and Deployment -- How will we roll this out safely into production? - - Are we deprecating something else? -- How often and with what means we will deploy this? -- What needs to be deployed and where? -- What are the risks when doing it? -- What are the dependencies when rolling out? - -### Monitoring -- Is there existing monitoring that will be used by this epic? -- If new monitoring is needed, it should be defined and alerting thresholds should be set up. - -### FR Hand off -- What documentation/information needs to be provided to FR so the team as a whole is successful in maintaining these changes? -- If you have created new monitoring rules - what tools/processes should FR use to troubleshoot alerts -- If existing monitoring is used, do the parameters need to be updated to accommodiate these new updates - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CServicing%20Guidelines.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CServicing%20Guidelines.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CServicing%20Guidelines.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/1ESManagedPoolsDesign.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/1ESManagedPoolsDesign.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/1ESManagedPoolsDesign.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/1ESManagedPoolsDesign.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,156 +0,0 @@ -## Motivation - -Due to corporate policy we are required to migrate our buildpool queues to 1ES Hosted pools. The deadlines include: new pools won’t be created after June 1st. Self-hosted pools will stop working after Sep 30th. - -Reference documentation for 1ES pools: -- [1ES hosted AzureDevOps Agents/Guidance](https://www.1eswiki.com//wiki/1ES_hosted_AzureDevOps_Agents%2fGuidance) -- [1ES hosted AzureDevOps Agents](https://www.1eswiki.com/wiki/1ES_hosted_AzureDevOps_Agents) -- [CloudTest Onboarding Guide](https://1esdocs.azurewebsites.net/test/CloudTest/How-Tos/Create-Update-Pool.html) - -High level migration plan looks like following: -- Create new definitions for each of our existing buildpools that will exist in parallel with the Helix queues -- Migrate customer Yamls to new 1ES based pools -- Delete old buildpool queues from Helix -- Clean up definitions for buildpools (i.e. remove all Helix-specific artifacts) -- Decomission all instances of pool provider and all related resources (key vaults, CI pipelines, release pipelines) - -We need to extend OSOB to create two new types of Azure resources: -- `Microsoft.CloudTest/image` resource for each image that we want to enable on the build pool. This resource contains reference to our SharedImageGallery image version. -- `Microsoft.CloudTest/hostedpool` resource for each pool. The pools will reference the CloudTest images mentioned above. - -## New AzDo pool distribution - -Right now, there are two pools (Prod and staging) for our internal project and two for external. We will keep one pool for staging per project and split prod pools into 3 different ones: XAML, servicing and R&D. - - -| AzDo Project | Enviroment | Current pool | New pools | Subscription | -| ------------ | ---------- | ------------ | --------- | ------------ | -| Internal | Staging | NetCoreInternal-Int-Pool | NetCore1ES-Internal-Int-Pool | HelixStaging | -| Internal | Prod | NetCoreInternal-Pool | NetCore1ES-Internal-Pool | HelixProd | -| Internal | Prod | NetCoreInternal-Pool | NetCore1ES-Xaml-Internal-Pool | DEP-UXP-WinUI-Helix | -| Internal | Prod | NetCoreInternal-Pool | NetCore1ES-Svc-Internal-Pool | dncenghelix-02 | -| Public | Staging | NetCorePublic-Int-Pool | NetCore1ES-Public-Int-Pool | HelixStaging | -| Public | Prod | NetCorePublic-Pool | NetCore1ES-Public-Pool | HelixProd | -| Public | Prod | NetCorePublic-Pool | NetCore1ES-Xaml-Public-Pool | DEP-UXP-WinUI-Helix | -| Public | Prod | NetCorePublic-Pool | NetCore1ES-Svc-Public-Pool | dncenghelix-02 | - -## 1ES Managed images - -1ES Managed images are stored in the resource group [1ESManagedImages](https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/84a65c9a-787d-45da-b10a-3a1cefce8060/resourcegroups/1ESManagedImages/overview) in **dnceng-internaltooling** subscription - -Each Managed image points to an Azure image in the Shared Gallery ([HelixImages](https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/84a65c9a-787d-45da-b10a-3a1cefce8060/resourceGroups/HelixImages/providers/Microsoft.Compute/galleries/HelixImages/overview)) where we store the images we build in **CreateCustomImage.exe** this includes Prod and Staging images. - -1ES Managed images used in prod are tagged the same way Azure images are, the tag is **IsProductionImage** and its value is **true**. The tagging happens during the deployment of the pools in **DeployHostedPools.exe**. We will keep the lasted 3 prod images the same way we do for Azure images, the clean up will happen in CleanPRs.exe - -## Customer impact - -All customers must change their yaml files to start using 1ES Host pools. Proper documentation will be shared through our Partners DL. - -The old syntax: -```yaml -pool: - name: NetCoreInternal-Pool - queue: BuildPool.Server.Amd64.VS2017 -``` - -Will be replaced by: -```yaml -pool: - name: NetCore1ES-Internal-Int-Pool - demands: ImageOverride -equals BuildPool.Server.Amd64.VS2017 -``` - -## OSOB changes - -- We will need to create new definitions in the YAML inheriting from existing `BuildPool.` queues. This new definitions will have the same properties as existing ones but we will have to change the names because we can't have two definitions with the same name. We can do it for example by changing prefix (e.g. `Build.Windows.10.Amd64` instead of `BuildPool.Windows.10.Amd64`). Keeping the old names would also be technically possible but it would require more changes in the OSOB deployment steps. - -- We will use the `Purpose` property to mark definitions used for 1ES hosted pool images. `DeployQueues` will skip this queue but it will be processed in the new `DeployManagedPools` step instead. It shouldn't require any changes to CreateCustomImages. - -- A new file called hostedpools.yaml will be create under definition-base folder. It will contain pools' metadata that later will be used during their deployment. The file will look like this: - -```yaml -HostedPools: -- Name: NetCore1ES-Internal-Pool - Subscription: HelixProd - VMSku: Standard_Dav4 - Region: westus2 - Size: 100 -- Name: NetCore1ES-Xaml-Internal-Pool - Subscription: DEP-UXP-WinUI-Helix - VMSku: Standard_Dav4 - Region: westus2 - Size: 100 -``` - -## 1ES hosted pool ARM template - -We will need to add a new step to OSOB build pipeline that will generate and deploy ARM template that will provision 1ES hosted pool. We could extend `DeployQueues` but it will probably be better idea to create new tool `DeployHostedPools` that can be run in parallel with `DeployQueues`. - -Example ARM template for 1ES hosted pool looks like following: - -```json -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "resources": [ - { - "name": "BuildPool.Windows.10.Amd64.Open", - "type": "Microsoft.CloudTest/images", - "apiVersion": "2020-05-07", - "location": "westus2", - "properties": { - "imageType": "Gallery", - "resourceId": "/HelixImages/BuildPool.Windows.10.Amd64.Open/2021.0423.210713" - } - }, - { - "name": "BuildPool.Ubuntu.1804.Amd64.Open", - "type": "Microsoft.CloudTest/images", - "apiVersion": "2020-05-07", - "location": "westus2", - "properties": { - "imageType": "Gallery", - "resourceId": "/HelixImages/BuildPool.Ubuntu.1804.Amd64/2021.0503.000052" - } - }, - // ... - { - "name": "NetCorePublic-1ESPool", - "type": "Microsoft.CloudTest/hostedpools", - "dependsOn": [ - "[resourceId('Microsoft.CloudTest/images', 'BuildPool.Windows.10.Amd64.Open')]" - "[resourceId('Microsoft.CloudTest/images', 'BuildPool.Ubuntu.1804.Amd64.Open')]" - // ... - ], - "apiVersion": "2020-05-07", - "location": "westus2", - "properties": { - "organization": "https://dev.azure.com/dnceng", - "sku": { - "name": "Standard_Dav4", - "tier": "Standard" - }, - "images": [ - { - "imageName": "BuildPool.Windows.10.Amd64.Open", - "poolBufferPercentage": "*" - }, - { - "imageName": "BuildPool.Ubuntu.1804.Amd64.Open", - "poolBufferPercentage": "*" - } - // ... - ], - "maxPoolSize": "100", - "agentProfile": { "type": "Stateless" } - } - } - ] -} -``` - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CServicingJobRedirection%5C1ESManagedPoolsDesign.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CServicingJobRedirection%5C1ESManagedPoolsDesign.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CServicingJobRedirection%5C1ESManagedPoolsDesign.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/DesignDocs.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/DesignDocs.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/DesignDocs.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/DesignDocs.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,81 +0,0 @@ -# Design doc for executing servicing Helix jobs in a COGS Azure subscription - -Epic link: https://github.com/dotnet/core-eng/issues/11639 - -## Motivation - -In order to reduce costs of running our `HelixProd` Azure subscription we want to redirect all servicing builds to `dncenghelix-02` subscription. The difference between those subscriptions is that `HelixProd` is assigned to R&D budget, `dncenghelix-02` on the other hand is assigned to COGS budget (Cost Of Goods Sold). The latter one should be used for all released software but currently we are using R&D budget for everything. Moving servicing builds to COGS-based subscription will allow us to optimize the usage of our current R&D bugdet. - -More information about how we use different subscription types can be found at the following links: -- [Guidance for Production and Non Production](https://dev.azure.com/devdiv/Engineering/_wiki/wikis/CNEKB/7968/Guidance-for-Production-and-Non-Production) -- [Categories of R&D Subscriptions in DevDiv](https://dev.azure.com/devdiv/Engineering/_wiki/wikis/CNEKB/10037/Categories-of-R-D-Subscriptions-in-DevDiv) - -All 2.1, 3.1 and 5.x builds and tests should be redirected. Any servicing branches that are created in the future will also have to be onboarded into this mechanism which must be taken into account in this epic. - -Parts of the changes in this epic will leverage the work we already did to enable team/repository based redirection for `runtime` and `xaml` subscriptions. The documentation on this can be found here: [Documentation/Project-Docs/RepositoriesInTheirOwnSubscription.md](https://github.com/dotnet/arcade/blob/main/Documentation/Project-Docs/RepositoriesInTheirOwnSubscription.md). - -Following diagram depicts high level view of the components that will be involved in the implementation and the general logic of redirecting jobs to queues located in separate subscriptions: - -![Design Diagram](ServicingBuildsRedirectDesign.svg) - -## Development work overview - -The changes have to be done in four areas: Helix SDK, pool provider, OSOB and Helix API. They are mostly independent of each other so can be done in any order but it makes the most sense to do changes in OSOB before we update Helix API. - -The needed changes are: - -### arcade-pool-provider - -Repository: https://github.com/dotnet/arcade-pool-provider - -Pool Provider has to send additional information in Helix API SendJob request - in particular the name of branch that is being built or the target branch for the PR. Based on this data Helix API will be able to differentiate servicing builds from normal ones. Luckily when AzDO calls our pool provider to acquire agent it gives us `getAssociatedJobUrl` property as part of the request and we can call this endpoint to get more detailed information about the job. We also receive `authenticationToken` from AzDO so there's no need to add additional secrets or configuration on our side. - -The process will look like this: -1. AzDO calls our pool provider to acquire new agent -2. We check if the base queue specified in request exists, if not we return error to AzDO. This check already exists so there are no changes needed here. -3. We add new HTTP request to `getAssociatedJobUrl` to get the data about the job -4. The response contains list of all build variables. We need to extract following ones: - - `build.reason` to distinguish between PR and non-PR builds - - for PR: at least `system.pullRequest.targetBranch` but it may be also useful to include `system.pullRequest.sourceBranch` and `system.pullRequest.sourceRepositoryUri` at the same time as they may be useful for debugging. - - for non-PR: at least `build.sourceBranch` and optionally also `build.repository.name`. -5. We store the extracted variables as properties on job creation request sent to Helix API -6. If `getAssociatedJobUrl` fails we send job anyway without additional properties to not block it's execution. It will be then sent to standard subscription by Helix API. - -When adding this functionality we have to pay attention to performance because: -1. We have hard limit of 30s to respond to each acquire agent call -2. AzDO sends acquire agent requests synchronously so any slowdown in processing of this call will affect other jobs irregardless if they are servicing or not - -For this reason we will need to prepare monitoring for pool provider acquire agent calls in Grafana. If the slowdown will be significant then we need to change pool provider so that it returns response earlier and then calls `getAssociatedJobUrl` and Helix API asynchronously in the background. - -### dotnet-helix-machines - -Repository: https://dev.azure.com/dnceng/internal/_git/dotnet-helix-machines - -We need to add new servicing queues in OSOB. We will duplicate the minimum set of queues that is currently needed for any servicing builds and instantiate the copies of them in `dncenghelix-02` subscription. New queues will inherit all settings from original queues in `HelixProd` subscription with the exception of new suffix `.svc`. For example there will exist queue called `windows.10.amd64.svc` and the related scaleset `windows.10.amd64.svc-a-scaleset` placed in `dncenghelix-02` subscription. This new queue and scaleset will have the same configuration as original `windows.10.amd64` queue and scaleset. This will be done in similar way as for `runtime` jobs redirection. An example can be found here: [definitions/shared/windows.yaml](https://dev.azure.com/dnceng/internal/_git/dotnet-helix-machines?path=%2Fdefinitions%2Fshared%2Fwindows.yaml&version=GC72af1ddb6e9ff7c7374512ccad6f78e93778066c&line=1754&lineEnd=1756&lineStartColumn=1&lineEndColumn=33&lineStyle=plain&_a=contents). As we can see in the example the servicing queue will inherit all future changes to the base queue. There will be technical possibility to modify servicing queue directly so that it diverges from the base queue that it was based on but to ease maintenance we will probably try to avoid this situation whenever possible. This will be the same approach we have chosen for `xaml` and `runtime` subscriptions (https://github.com/dotnet/core-eng/issues/10630#issuecomment-707224671). - -By querying Kusto we can find around 57 queues (out of total 108 non-onprem queues) that were used by servicing branch in last 200 days. Copying only those queues that are actually used for servicing lowers the overall number of resources we have to manage but on the other hand it means that we will have to create new queues after any new servicing branches are created in the future. We will need to prepare the documentation on how to do it and put it into some post-release checklist if we have one. We will also have monitoring in place that will catch any cases in which we missed some queues. - -### dotnet-helix-service - -Repository: https://dev.azure.com/dnceng/internal/_git/dotnet-helix-service - -We need to extend the logic in Helix API to check additional job properties specified in request - in particular the branch name. If the job is related to servicing branch (i.e. `release/X.X`) or PR targeted at servicing branch then the `.svc` suffix will be appended to the queue name specified in original request and job will be redirected to this new queue (e.g. from `windows.10.amd64` to `windows.10.amd64.svc`). - -If the servicing queue does not exist for any reason then the job will be sent to standard queue to not block it's execution but we will log an error in AppInsights and have Grafana alert that will notify us about this situation. - -The changes have to be made in `ValidateAndNormalizeRequest` controller method: [JobController.cs#L104](https://dev.azure.com/dnceng/internal/_git/dotnet-helix-service?path=%2Fsrc%2FServiceFabric%2FHelix%2FHelixAPI%2FApi%2Fv2019_06_17%2FControllers%2FJobController.cs&version=GBmaster&line=104&lineEnd=105&lineStartColumn=1&lineEndColumn=1&lineStyle=plain&_a=contents) - -This mechanism will be done in similar way as currently implemented team-based redirection (`runtime`/`xaml`) - the difference will be that we will check source branch instead of source repository when redirecting jobs. This servicing branch check will take priority over the team check. - -### arcade (Helix SDK) - -Repository: https://github.com/dotnet/arcade - -Currently we miss the name of PR target branch for test jobs so wee need to extend the `SendHelixJob` task to include that property. We already copy some variables here: [SendHelixJob.cs#L219](https://github.com/dotnet/arcade/blob/d244d21e54bd1778ae68b3ecf676e3c95fffac2e/src/Microsoft.DotNet.Helix/Sdk/SendHelixJob.cs#L219) so it should just require to add `system.pullRequest.targetBranch` to that list. After that we will also need to update the version of Helix SDK package in existing servicing branches. - -For 2.1 servicing branches it may be necessary to manually update the files in each branch because they don't use the same mechanisms as newer 3.1/5.0 Helix SDK-based builds. There are using groovy scripts and `upload-tests.proj` files (and maybe even other mechanisms) to schedule tests in Helix. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CServicingJobRedirection%5CDesignDocs.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CServicingJobRedirection%5CDesignDocs.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CServicingJobRedirection%5CDesignDocs.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/One-Pager.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/One-Pager.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/One-Pager.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/One-Pager.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,46 +0,0 @@ -# Execute servicing Helix jobs in a COGS Azure subscription -## Stakeholders -The main stakeholder of the project is Chris Bohm as the .NET Azure Champion - -## Risk -More risk comes from the lack of experience the team working on Helix API projects, there will be a ramp-up on how development and debugging works in these projects. - -No proof of concept will be needed as we already redirect jobs based on the repository that sends them. We will expand this adding to build and now redirect jobs based on the target branch. - -We have all the dependencies in place to start working on redirecting work. - -This will not require any change from our customers, servicing jobs will be redirected once a servicing branch exists for them. - -Completing this work will open the possibility to increase the load in our vNext Helix queues if needed so the longer it takes to complete the project the higher the risk of not having enough R&D budget for future workload. - -## Serviceability -Unit tests will be added to ensure servicing builds and tests jobs are identified by our services and then redirected appropriately. -Daily validation will be added to our staging environment where the code paths added are executed and alerts will be triggered if unexpected behavior is identified. - -No new secrets will be added as part of this effort - -This project has no SDL implications. - -This epic will not modify the current steps for setting up repro/test/dev environments for Helix API. Any missing information in the current documentation will be added -## Rollout and Deployment -The epic doesn't consider any breaking changes on how helix services are deployed to production but feature flags will be added during the development to reduce the need of rollback in case of unexpected results in production. -The epic isn't deprecating any service. - -The impacted services will follow the current deployment schedule which considers one deployment to production every Wednesday. - -New queues will need to be created when the servicing OS matrix changes after a release. -Changes for redirecting work will be added to Helix API during the development of the project but won't be required once the epic is completed. - -The risk of running production deployments for these services is low as they are mature services and have been executing successful builds for a long time. In case of having bugs in the payload that prevents the work from being redirected, the customers most likely won't be impacted as all the work will executed in the queue originally used in the job. - -## Usage Telemetry -We will add a new property to servicing jobs to mark them so they can query in Kusto for monitoring and alerting. - -## Monitoring -S360 report will show us how our R&D bill goes down while the COGS bill increases. -New Grafana charts will be created to show the subscription where the servicing runs are being executed and an alert, that gets triggered when a servicing job is executed in a R&D queue, will be added. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CServicingJobRedirection%5COne-Pager.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CServicingJobRedirection%5COne-Pager.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CServicingJobRedirection%5COne-Pager.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/ServicingBuildsRedirectDesign.svg dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/ServicingBuildsRedirectDesign.svg --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/ServicingBuildsRedirectDesign.svg 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/ServicingJobRedirection/ServicingBuildsRedirectDesign.svg 1970-01-01 00:00:00.000000000 +0000 @@ -1,1696 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - VBackground-1 - - - - - - - - - Currency.1001 - - Sheet.1002 - - - - - - - - Sheet.1003 - - - - Sheet.1004 - - - - Sheet.1005 - - - - Sheet.1006 - - - - Sheet.1007 - - - - Sheet.1008 - - - - Sheet.1009 - - - - Sheet.1010 - - - - Sheet.1011 - - - - Sheet.1012 - - - - Sheet.1013 - - - - Sheet.1014 - - - - Sheet.1015 - - - - Sheet.1016 - - - - Sheet.1017 - - - - Sheet.1018 - - - - Sheet.1019 - - - - Sheet.1020 - - - - Sheet.1021 - - - - Sheet.1022 - - - - Sheet.1023 - - - - Sheet.1024 - - - - Sheet.1025 - - - - - - - - - Helix API - - - - - Rectangle.142 - HelixProd subscription - - - - - - - HelixProd subscription - - Rectangle.141 - ​DEP-UXP-WinUI-Helix subscription - - - - - - - ​DEP-UXP-WinUI-Helix subscription - - Rectangle.139 - ​dnchelix-02 subscription - - - - - - - ​dnchelix-02 subscription - - App Services.1 - Helix API - - - - - Sheet.2 - - Sheet.3 - - - - - - - Sheet.4 - - - - - - - Sheet.5 - - - - - - - Sheet.6 - - - - - - - Sheet.7 - - - - - - - Sheet.8 - - - - - - - Sheet.9 - - - - - - - Sheet.10 - - - - - - - Sheet.11 - - - - - - - Sheet.12 - - - - - - - Sheet.13 - - - - - - - - - - Helix API - - - Decision.15 - Target branch is servicing branch - - - - - - - - - - - - - - - - - - - Target branch is servicing branch - - Decision.16 - Source repo has own subscription (Rt/Xaml) - - - - - - - - - - - - - - - - - - - - Source repo has own subscription (Rt/Xaml) - - Process.37 - Send job to servicing queue - - - - - - - - - - - - - - - - - - - Send job to servicing queue - - VM Scale Sets.38 - X.svc - - Sheet.39 - - - - - - - Sheet.40 - - - - - - - Sheet.41 - - - - - - - Sheet.42 - - - - - - - Sheet.43 - - - - - - - Sheet.44 - - - - - - - Sheet.45 - - - - - - - Sheet.46 - - - - - - - Sheet.47 - - - - - - - Sheet.48 - - - - - - - Sheet.49 - - - - - - - Sheet.50 - - - - - - - Sheet.51 - - - - - - - Sheet.52 - - - - - - - Sheet.53 - - - - - - - - - X.svc - - - Dynamic connector.56 - - - - Process.57 - Add team name suffix and redirect request to that queue - - - - - - - - - - - - - - - - - - - Add team name suffix and redirect request to that queue - - VM Scale Sets.58 - X.xaml - - Sheet.59 - - - - - - - Sheet.60 - - - - - - - Sheet.61 - - - - - - - Sheet.62 - - - - - - - Sheet.63 - - - - - - - Sheet.64 - - - - - - - Sheet.65 - - - - - - - Sheet.66 - - - - - - - Sheet.67 - - - - - - - Sheet.68 - - - - - - - Sheet.69 - - - - - - - Sheet.70 - - - - - - - Sheet.71 - - - - - - - Sheet.72 - - - - - - - Sheet.73 - - - - - - - - - X.xaml - - - Dynamic connector.75 - ​YES - - - - - ​YES - - Process.76 - Send job to the queue specified in initial request - - - - - - - - - - - - - - - - - - - Send job to the queue specified in initial request - - VM Scale Sets.77 - X - - Sheet.78 - - - - - - - Sheet.79 - - - - - - - Sheet.80 - - - - - - - Sheet.81 - - - - - - - Sheet.82 - - - - - - - Sheet.83 - - - - - - - Sheet.84 - - - - - - - Sheet.85 - - - - - - - Sheet.86 - - - - - - - Sheet.87 - - - - - - - Sheet.88 - - - - - - - Sheet.89 - - - - - - - Sheet.90 - - - - - - - Sheet.91 - - - - - - - Sheet.92 - - - - - - - - - X - - - Dynamic connector.93 - ​NO - - - - - ​NO - - Process.102 - Send job request to Helix API with queue name = "X" - - - - - - - - - - - - - - - - - - - - Send job requestto Helix APIwith queue name = "X" - - Dynamic connector.103 - - - - Dynamic connector.104 - - - - Azure DevOps.5 - Azure DevOps - - Sheet.106 - - - - - - - - - Azure DevOps - - - App Services.7 - Pool Provider - - - - - Sheet.108 - - Sheet.109 - - - - - - - Sheet.110 - - - - - - - Sheet.111 - - - - - - - Sheet.112 - - - - - - - Sheet.113 - - - - - - - Sheet.114 - - - - - - - Sheet.115 - - - - - - - Sheet.116 - - - - - - - Sheet.117 - - - - - - - Sheet.118 - - - - - - - Sheet.119 - - - - - - - - - - Pool Provider - - - Dynamic connector.106 - - - - Dynamic connector.107 - - - - Process.104 - Request from AzDO to acquire agent for queue "X" - - - - - - - - - - - - - - - - - - - - Request from AzDO to acquire agent for queue "X" - - Dynamic connector.123 - - - - - - - Browser.133 - Helix SDK - - Sheet.134 - - - - Sheet.135 - - - - Sheet.136 - - - - - - Helix SDK - - - Dynamic connector.137 - - - - Rectangle.145 - dnchelix-01 subscription - - - - - - - dnchelix-01 subscription - - VM Scale Sets.146 - X.rt - - Sheet.147 - - - - - - - Sheet.148 - - - - - - - Sheet.149 - - - - - - - Sheet.150 - - - - - - - Sheet.151 - - - - - - - Sheet.152 - - - - - - - Sheet.153 - - - - - - - Sheet.154 - - - - - - - Sheet.155 - - - - - - - Sheet.156 - - - - - - - Sheet.157 - - - - - - - Sheet.158 - - - - - - - Sheet.159 - - - - - - - Sheet.160 - - - - - - - Sheet.161 - - - - - - - - - X.rt - - - Rectangle.162 - ​nethelix service bus - - - - - - - nethelix service bus - - - - - Service Bus.163 - X.svc queue - - Sheet.164 - - - - Sheet.165 - - - - Sheet.166 - - - - Sheet.167 - - - - Sheet.168 - - - - Sheet.169 - - - - - - X.svc queue - - - - - - Service Bus.170 - X.rt queue - - Sheet.171 - - - - Sheet.172 - - - - Sheet.173 - - - - Sheet.174 - - - - Sheet.175 - - - - Sheet.176 - - - - - - X.rt queue - - - - - - Service Bus.177 - X.xaml queue - - Sheet.178 - - - - Sheet.179 - - - - Sheet.180 - - - - Sheet.181 - - - - Sheet.182 - - - - Sheet.183 - - - - - - X.xaml queue - - - - - - Service Bus.184 - X queue - - Sheet.185 - - - - Sheet.186 - - - - Sheet.187 - - - - Sheet.188 - - - - Sheet.189 - - - - Sheet.190 - - - - - - X queue - - - Dynamic connector.191 - - - - Dynamic connector.192 - - - - Dynamic connector.193 - - - - Dynamic connector.195 - - - - Dynamic connector.196 - - - - Dynamic connector.197 - - - - Dynamic connector.198 - - - - Dynamic connector.199 - - - - Decision.200 - Servicing queue exists - - - - - - - - - - - - - - - - - - - Servicing queue exists - - Dynamic connector.201 - ​YES - - - - - ​YES - - Dynamic connector.202 - ​YES - - - - - ​YES - - Dynamic connector.204 - ​NO - - - - - ​NO - - Start/End.1001 - New build job scheduled by AzDO - - - - - - - - - - - - - - - - - - - - New build job scheduled by AzDO - - Start/End.1002 - New test job created by the MSBuild task - - - - - - - - - - - - - - - - - - - - New test job created by the MSBuild task - - Dynamic connector.1003 - - - - Dynamic connector.1004 - - - - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/source-build-orchestration.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/source-build-orchestration.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/source-build-orchestration.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/source-build-orchestration.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,96 +0,0 @@ -# Source Build Orchestration - -## Overview - -.NET Core contains two logical products: Shared Framework (which is a runtime + -a set of managed libraries) and an SDK (which is managed code running on top of -the shared framework to provide commands like: `dotnet run`, `dotnet restore`, -`dotnet build`, etc. - -Each of these products is made up from multiple git projects which operate -independently from one another. We use NuGet packages and zip/tar.gz files to -hand off artifacts across project boundaries. - -At a high level, composing these projects into products is done by computing the -dependency relationship between all the projects then building from the bottom -of the dependency tree upwards. As we build, we use the locally built artifacts -(nupkgs and zip/tar.gz files) instead of versions we would obtain from NuGet, MyGet or -Azure Blob Storage to satisfy the needs of projects which depend on a specific -component. - -In this way, we may elide building certain projects when building a product (for -example, a CoreCLR developer may want to use a source built version of CoreCLR, -but use the existing set of packages for the rest of .NET Core). - -## Bootstrapping - -We require an existing .NET Core in order to build .NET Core. This is due to the -dependencies on `dotnet restore` (to consume NuGet packages) as well as -dependencies on MSBuild and Roslyn for compiling the managed code in the -product. We already have a set of scripts which can be used to bootstrap an -existing version of the .NET Core SDK on a new Linux distribution (by rebuilding -the native code from source) which we use when bringing new distributions -online. - -Some projects carry their own copies of CoreCLR (instead of obtaining a copy of -the .NET Core SDK and then running on top of that) which is a practice we should -stop. Long term, we need to get to a world where all projects uses a single -shared version of the Shared Framework and SDK when they need to invoke managed -code as part of their build. Ideally, this will always be the last released -version of the .NET Core SDK from the LTS train (e.g. when building 1.0.X and -1.1.X we rely on a pre-existing .NET Core 1.0.0 SDK) so that a source built -version of the previous product can be used without having to do any -bootstrapping. - -At the start of a source build, if an existing toolchain is not present, we'll -use the bootstrapping script (Rover) to get a working toolchain and then use -that to start building. - -## Expressing dependencies with NuGet packages - -We'll continue to use NuGet packages as a way of handing off artifacts between -projects. Note that NuGet packages should **only** be used for cross project -dependencies. When depending on a component built out of the same git -repository, project references should be used instead. The rationale for this -rule is that without it we can't build all of a repositories dependencies from -source, since it depends on a previous version of itself. - -We use NuGet packages instead of some other format (e.g. building to a shared -directory with a well known convention for artifacts) because it provides the -most flexibility across projects. For better or worse we understand how to use -NuGet across repositories to manage dependencies. - -It is possible that the NuGet packages that we consume as part of a composed -build do not match the NuGet packages we would ship. For example, a NuGet -package may provide both Desktop and .NET Core versions of an asset. A project -may choose not to building Desktop artifacts if just a .NET Core build was -requested. In this case, the repository should produce a "partial package" which -contains only a subset of the assets. This package should however have the same -identity and version as the full package. We have already added support in -BuildTools for projects that wish to do this. - -## Building Projects - -When the "repo api" is implemented fully across all the projects that make up -.NET Core, we can use it to construct a build graph and start building -dependencies. In the short term, we'll hard code the layering diagram of our -repositories into build scripts themselves. - -To begin, we'll start by building the `dotnet/standard` repository to produce -the set of .NET Standard 2.0 reference assemblies. If there are additional sets -of reference assemblies we'll need during the build, we should introduce a -`dotnet/reference-assemblies` style repositories which can built from source a -set of refs that can be used to target other profiles. - -After a project has been built we move all of the nupkgs it produced into a -package fallback location and use the normal NuGet APIs to consume them. In -addition, we'll use the repo api "change" command to update the versions that -dependent projects consume. - -We continue building projects and updating dependencies until the entire product -has been built. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5Csource-build-orchestration.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5Csource-build-orchestration.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5Csource-build-orchestration.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Toolset/Overview.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Toolset/Overview.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Toolset/Overview.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Toolset/Overview.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -Moved to https://github.com/dotnet/arcade/blob/main/README.md - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CToolset%5COverview.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CToolset%5COverview.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CToolset%5COverview.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Toolset/PublishConsumeContract.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Toolset/PublishConsumeContract.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Toolset/PublishConsumeContract.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Toolset/PublishConsumeContract.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,188 +0,0 @@ -# Toolset Packages - -- [Toolset Feed](#toolset-feed) -- [Core Tools SDK](#core-tools-sdk) -- [Bootstrapping](#bootstrapping) -- [Using tools in non-bootstrapping scenarios](#using-tools-in-non-bootstrapping-scenarios) -- [Onboarding](#onboarding) -- [Package versioning](#toolset-package-versions) -- [Package contents](#package-contents) -- [Package symbols](#package-symbols) -- [Maestro and the Versions repo](#maestro-and-the-versions-repo) -- [Gallery](#gallery) -- [Package validation](#package-validation) -- [Sdk validation](#sdk-validation) -- [Provenance](#provenance) -- [Usage](#usage) - -## Toolset Feed - -Toolset packages should be published to a consistent location for consumption. - -There are currently a couple of different sources for various repo toolsets. - -- https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json -- https://dotnet.myget.org/F/roslyn-tools/api/v3/index.json -- https://dotnet.myget.org/F/dotnet-buildtools/api/v3/index.json - -Shared toolset packages will be published to a single location so that consumption / [discoverability](#gallery) is simplified. - -Toolset package feed: https://dotnetfeed.blob.core.windows.net/dotnet-tools-internal/index.json - -## Core Tools SDK - -The core tools SDK is the entry point for toolset functionality. We will provide a core SDK which repo's will consume as an SDK (or package reference) that provides functionality for tasks that are common across repo's. The core tools SDK may contain one or more tools packages which have been determined to be beneficial to a common set of repos (most?) across DotNet. As packages prove valuable to more than one repo, they will be considered for inclusion in the core tools SDK. However, we want to be considerate of package bloat and seek alternative (but common) means of consumption for tool packages which do not meet the critera for inclusion in the core tools SDK. In other words, the packages will need to provide clear benefit to the majority (or all) of repos in order to be considered for inclusion in the core tools SDK. - -## Tools packages - -Tools packages provide functionality (MSBuild or other) which are useful to one ore more repo's. Tools packages (specifically MSBuild task packages) are currently being [discussed](https://github.com/dotnet/core-eng/pull/2541/files) and will be considered for inclusion in the core tools SDK (if they provide clear functionality to the majority of DotNet repos). Additional tools / task packages will be available for direct consumption or via the core tools SDK. - -## Bootstrapping - -Bootstrapping a repo will consist of using the CLI (obtainable via a script from a well-known / secure location) to restore the [core tools SDK](#core-tools-sdk) project. - -## Using tools in non-bootstrapping scenarios - -There are some scenarios where bootstrapping is not ideal for acquiring tools. These are scenarios which are not project based, or not tied to a specific repo. A primary example of this is telemetry, where you want to be able to send information about a build, before a repo has even bootstrapped. Another may be orchestration (depending on implementation), the orchestration may schedule and report on multiple repo's, but itself is not tied to a repo. For these scnearios, we would like to be able to provide common tooling. At this point, there are a couple of ideas being thrown around. - -- "DotNet CLI install tools" is one option for local toolset installs, but not available until .NET Core 2.1 Preview 2 (at the earliest). -- "Shared Library" model (like Jenkins), where tools are provided via another common tools repo. -- [CBT](https://cbt-userguide/Introduction.html) is a new offering from 1ES. Not enough investigation has occurred to determine if this is a viable option. - -We will evaluate guidance for these scenarios when they arise. - -## Onboarding - -Onboarding a repo to the toolset will be a [simple process](https://github.com/dotnet/arcade/tree/main/Documentation/Project-Docs/buildtools-bootstrap.md). - -We will provide links to zips / tarballs to acquire the basic pieces necessary for bootstrapping the core tools SDK on a supported platform. - -Note: Further guidance on onboarding a repo and customizing for a particular repo's needs will be provided in a separate documentation. Some general [usage](#usage) is provided below. - -## Toolset package versions - -Package versioning should follow precedent set by other repo's rather than trying to produce new versioning scheme / tooling. Most of the "core" DotNet repositories (CoreFx, CoreClr, Core-Setup, etc...) are using [versioning](https://github.com/dotnet/corefx/blob/master/Documentation/building/versioning.md) tools which are a part of [BuildTools](https://github.com/dotnet/buildtools/blob/master/src/Microsoft.DotNet.Build.Tasks/PackageFiles/versioning.targets). The versioning logic will be available from a [task package](https://github.com/dotnet/core-eng/pull/2541/files) where it will be generally available for all participating repositories. - -### Versioning constraints - -- Version needed to be higher than the versions previously shipped. -- There needs to be an ability to have multiple versions per day. -- Versions need to be always increasing. -- Version needs to be lower than 65535 (unsigned short int max) since the version is used as assembly file version which has that constraint. -- Version needs to be reproducible. -- We shouldn't have the need to check in a file containing the buildnumber. Checked in files containing major/minor/patch will be permitted. -- We will support SemVer [1.0](https://semver.org/spec/v1.0.0.html) and [2.0](https://blog.nuget.org/20140924/supporting-semver-2.0.0.html) semantics. If there are issues related to SemVer 2.0 support on older clients, then we'll consider adjusting to support those scenarios. - -Package version example: - -```Text -SemVer 1.0: mylibrary.1.0.0-prerelease-00001-01.nupkg -SemVer 2.0: mylibrary.1.0.0-prerelease.1.1.nupkg -``` - -## Package contents - -Standard package layout - -``` Text -(root) - - sdk/ - + Sdk.props (optional) - + Sdk.targets - - build/ - + $packageId.props (optional) - + $packageId.targets - - netstandard1.5/ - + $taskAssembly.dll - - net46/ - + $taskAssembly.dll -``` - -The standard package layout *supports* (not required) consuming packages as [MSBuild Project SDKs](https://docs.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk). In general, we believe that there will be one project SDK which is referenced and that the toolset packages will be consumed as package references, not as SDK's. At this time, however, we are not enforcing a strict model which prevents or requires toolset consumption as individual SDK's. - -`Sdk.props` and `Sdk.targets` should not contain any functional code, only imports for the respective build props / targets. - -### Requirements - -- Utilities, exe's, scripts, etc which are part of the package functionality must be usable via MSBuild properties / targets. You should not have a collection of executable files in your package which do not include MSBuild entry points for using them. - -- Additional package guidelines are outlined [here](https://github.com/dotnet/arcade/blob/main/Documentation/Project-Docs/Toolshed/TaskPackages.md#implementation-details) - -- Packages need to include accountability information in the nuspec. At a minimum, source repository link and commit SHA. - -### Package dependencies - -The tools provided via NuGet packages for MSBuild tasks will be self-contained (include all of their dependenices). It is important to be deliberate about what dependency versions are included in a package because otherwise the mix-match model of the tools will be broken. As a starting place, dependency versions should align with what is provided by the core tools SDK. If you have additional dependencies outside of those in the core tools SDK (or need to change dependency versions), then we should be deliberate (have a conversation with core tools stakeholders) about what those dependencies are and what versions are required. - -### Best practices - -- Choose non-generic build property / target names. Packages should be very considerate when defining property / target names. For example, if each package defines a property called `TaskDir` which is defined as `$(MSBuildThisFileDirectory)build/blah`, then the last package imported will be the one to define `TaskDir`, and all of your other packages will be broken. So packages should prefer to choose target / property names which are unlikely to conflict with other packages or which include the package name in the property / targetname, ie `MyPackageNameTaskDir` - -- Ensure build props file is imported. In the props file, you should define some property such as `<_MyPackageNameImported>true` and the targets file then includes ``. This would permit consumers to just directly import the targets file if desired instead of importing two files. - -## Package symbols - -Task package symbols should be embedded in the binaries. - -## Maestro and the Versions repo - -Toolset packages will assume the use of Maestro for automatic version uptake. - -Toolset packages should be publishing version information to the versions repo so that respositories using automatic version updating can consume them. When publishing, there should be package versions entries both for the repo producing the package, and for a tools location which aggregates the various toolset packages. [Details are TBD] - -## Gallery - -A traditional gallery (ie myget.org) is not provided for the toolset. Instead toolset packages may be browsed using a [package source](https://docs.microsoft.com/en-us/nuget/tools/package-manager-ui#package-sources) in Visual Studio. Additionally, toolset packages will be listed on the versions repo [link TBD]. - -## Package validation - -Currently, there are no unit tests for package validation / conformance. - -## Sdk validation - -Currently, there are no unit tests for Sdk validation / conformance - -## Provenance - -Security is continuing to tighten, and we require provenance for any bits that we own / control directly. Provenance guidance / requirements are provided [here](https://securityguidance.cloudapp.net/). It is important to keep these rules in mind for all tools package providing repos. - -## Usage - -### Core Tools SDK Usage - -The core tools SDK will be typically consumed as a [project SDK](https://docs.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk). - -### Tools packages Usage - -Tools packages will typically be consumed as package references in an individual repo. The toolset SDK should provide extensibility points to add package references for the toolset which are specific to a repo. If functionality proves to be beneficial to additional repo's, it will go under consideration for becoming part of the core toolset SDK. - -[Note: Extensibility points may not yet be present] - -Example of common `Toolset.proj` - -```XML - - - net462 - https://dotnetfeed.blob.core.windows.net/dotnet-tools-internal/index.json - -
-``` - -[Note: This example should include how to add project specific PackageReferences to the toolset] - -Example of tools as SDK's usage (less common usage) - -```XML - - - net462 - https://dotnetfeed.blob.core.windows.net/dotnet-tools-internal/index.json - -
-``` - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CToolset%5CPublishConsumeContract.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CToolset%5CPublishConsumeContract.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CToolset%5CPublishConsumeContract.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Toolset/TaskPackages.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Toolset/TaskPackages.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/Toolset/TaskPackages.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/Toolset/TaskPackages.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,195 +0,0 @@ -Task Packages -============= - -Task packages provides a set of commonly used MSBuild tasks that are not included in MSBuild itself. - -## Tasks - -Each task package must be self-contained in that it cannot define dependencies on other task packages (see [below](#no-dependencies).) -Task packages should avoid grouping too many tasks into the same place. - -### Reducing duplicate effort - -One reason to put tasks into shared packages is to reduce duplication in infrastructure code. -We currently have multiple implementations of similar tasks in use by .NET Core and ASP.NET Core projects. -Each is maintaining their own version. - -Examples: - - https://github.com/dotnet/cli/blob/master/build/Microsoft.DotNet.Cli.tasks - - https://github.com/aspnet/BuildTools/tree/dev/modules/KoreBuild.Tasks - - https://github.com/dotnet/buildtools/tree/master/src/Microsoft.DotNet.Build.Tasks - - https://github.com/dotnet/core-setup/tree/master/tools-local/tasks - -Some of these tasks of nearly identical behavior but just use different names. Some examples: - - - `GenerateFileFromTemplate` / `ReplaceFileContents` / `PreprocessFile` - - `SetEnvVar` / `SetEnvironmentVariable` - - `UnzipArchive` / `ZipFileExtractToDirectory` - -## Usage - -Tasks packages are distributed as a NuGet package using existing NuGet mechanisms. Developers can use them in MSBuild projects in the following ways: - -### Sdk element (recommended) - -Reference the package as an "SDK" in your MSBuild project. MSBuild 15.6 and up will automatically restore and extract this package. - -```xml - - - - - - - -``` - -```js -// global.json -{ - "msbuild-sdks": { - "Microsoft.DotNet.Build.Tasks.IO": "1.0.0" - } -} -``` - -**Best practice**: although SDK versions can be specified in .proj files, it is recommended to use global.json to ensure the SDK version -is consistent within a solution. - -### PackageReference (pre MSBuild 15.6) - -Reference the project as a PackageReference in csproj files. It is strongly recommended to set `PrivateAssets="All"` to avoid this package ending up in generated nuspec files. - -```xml - - - - - - - - - -``` - -### packages.config (NuGet 2/MSBuild 14) - -Use `NuGet.exe install packages.config` to download the package -```xml - - - -``` - -From your MSBuild project, import `Sdk.props` and `Sdk.targets` from the extract package location. -```xml - - - - - - - - - -``` - -## Implementation details - -Task packages have this layout - -``` -(root) - - sdk/ - + Sdk.props - + Sdk.targets - - build/ - + $packageId.props - + $packageId.targets - - netstandard1.5/ - + $taskAssembly.dll - - net46/ - + $taskAssembly.dll -``` - -Packages have the following metadata in their nuspec. - -```xml - - - -``` - -### No dependencies - -MSBuild task packages cannot have dependencies (due to the current design of the NuGet SDK resolver: https://github.com/Microsoft/msbuild/issues/2803). - -```xml - - - - - - - -``` - -### Examples - -The following are examples of tasks that we would like to build into common shared packages. -The implementation and naming is still subject to further review. -This list contains a set of tasks that appear to be commonly used across several repos. - -Microsoft.DotNet.Build.Tasks.IO - - `DownloadFile` - downloads a file. - - `ZipArchive` - creates a .zip file - - `UnzipArchive` - unzips a .zip file - - `GenerateFileFromTemplate` - supports a very simple templating format for key/value substitutions in a file - - `ComputeChecksum` - computes the SHA256 or SHA512 checksum for files - - `Chmod` - change Unix permissions - -Microsoft.DotNet.Build.Tasks.Git - - `GetGitCommitHash` - reads the current commit hash from a .git folder without needing git.exe installed - - `GetGitCommitBranch` - reads the current brancn name - -Microsoft.DotNet.Build.Tasks.Shell - - `Run` - like `Exec`, but it handles the complexity of escaping quotes and spaces in arguments - - `RunDotNet` - like `Run`, but launches a process using the same `dotnet.exe` file used to launch the current MSBuild process. Espcially useful for longing .NET Core console build tools - - `FindDotNetPath` - finds the `dotnet.exe` path on a machine - - `SetEnvironmentVariable` - sets an environment variable - -Microsoft.DotNet.Build.Tasks.NuGet - - `PackNuspec` - packages a .nuspec file - - `DownloadNuGetPackage` - fetches a package from a NuGet feed - - `PushNuGetPackages` - pushes NuGet packages in parallel - - `ReadNuGetPackageIdentity` - opens a .nupkg file and reads the package ID and version from its metadata - -Microsoft.DotNet.Build.Tasks.AzureStorage - - `UploadBlobToAzure` - pushes a blob to Azure Storage account - -### Packages (by team) which should be shared to start - -**ASP** - - Tasks - - DownloadFile - - ZipArchive - - UnzipArchive - - GenerateFileFromTemplate - - ComputeChecksum - - Chmod - -**Roslyn/CLI** - - Repack - - Signtool - - **CoreFx/CoreCLR** - - BlobFeed - - VersionTools/Dependency update - - Repo Tools - - ILAsm - - ILLinker - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CToolset%5CTaskPackages.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CToolset%5CTaskPackages.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CToolset%5CTaskPackages.md) - diff -Nru "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/V3 Publishing/one-pager.md" "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/V3 Publishing/one-pager.md" --- "/tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/V3 Publishing/one-pager.md" 2023-10-18 18:08:29.000000000 +0000 +++ "/tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/V3 Publishing/one-pager.md" 1970-01-01 00:00:00.000000000 +0000 @@ -1,55 +0,0 @@ -## V3 Publishing -We need to retire V1 and V2 publishing. - -Why do we need to retire V1 and V2? -Both V1 and V2 use multi stage(s) publishing infrastructure. V3 on the other hand uses single stage publishing, thereby reducing UI clutter. V3 reduces the number of machines used during publishing, which speeds up the whole process. In both V1 and V2, when new channels are added it requires an arcade update to the customer repository, but in V3 it will only require arcade getting an arcade update. - -Currently arcade release/5.0, main and all the repos getting updates from these branches are already using V3 publishing. In this epic we are planning to move arcade release/3.0 branch to use V3 publishing. We need all the repos currently which takes updates from arcade release/3.0 to use the latest V3 publishing. Also removing all the legacy publishing code that includes V1 and V2 publishing from arcade main and release/3.0 branches. - -Also will be working on ways to improve the performance of publishing artifacts and symbols, and add more tests during this process. This will include better way of downloading artifacts to improve publishing performance. - -## Stakeholders -- .NET Core Engineering -- .NET Core Engingeering Partners - -## Risk -What are the unknowns? -- How arcade-services would react to this publishing arcade update, because right now we have special stages in arcade-services compared to other repos which consumes update from arcade/release-3.0. -- While on-boarding repos to V3, there might be some risk because of some unknown dependency of the repo on V1/V2 publishing. - -## Rollout and Deployment -V1/V2 to V3 -a) We are deprecating legacy publshing code. This functionality will be first tested in arcade main and then in arcade-validation. Upon successful test, since all the repos getting update from arcade main are currently using V3 publishing. This rollout is going to be seamless. This is just going to be an arcade update and repo owners do not have to do anything here. -b) Then V3 publishing infrastructure has to be added in arcade/release-3.0 and this will be tested against some repos that takes update from arcade/release-3.0. Upon successful testing, an arcade update will be rolled out which customer repos have to consume. -c) Make a list of all the repos that will require to update like we did for arcade/release-5.0 eg:(https://github.com/dotnet/arcade/blob/main/Documentation/V3StatusUpdate.md) -d) Will send out an email to partners to upgrade from V1/V2 to V3 and help them upgrade to V3. Documentation on how to upgrade can be found here (https://github.com/dotnet/arcade/blob/main/Documentation/CorePackages/Publishing.md#how-to-upgrade-from-v2-to-v3) -e) After all the repos are onboarded successfully, V1 and V2 publishing infrastructure will be deprecated from arcade/release-3.0. This is going to be an arcade rollout which customers repos have to consume. - -Performance improvements -a) All the performance related improvements are going to be an arcade update which customer repos have to consume. This will be tested against runtime, installer before roll out. - -## Serviceability -Testing -a) While improving the performance of publishing artifacts and symbols, tests will be added to cover downloading artifacts. -b) While deprecating legacy publishing, some V2 publishing tests will replaced by V3 publishing tests. -c) Some tests related to PublishArtifactsInManifest, SettingUpV3Config and Symbol publishing are already in place and can be found here (https://github.com/dotnet/arcade/tree/main/src/Microsoft.DotNet.Build.Tasks.Feed.Tests) - -PATs -a) No new PATs are added as part of this epic. - -SDL -No change to the SDL threat model. - -Confidence in deployments/shipping -a) Before on-boarding repos using arcade/release-3.0 on V3 publishing, a subset of repos will be tested with the latest update, only upon successful test the repos will be on-boarded. -b) Adding more tests to the publishing infrastructure will increase the confidence. - -## Monitoring -Customers are responsible for keeping their build green once the changes are rolled out. - -## FR Hand off -Publishing FAQs are already in place here (https://github.com/dotnet/arcade/blob/main/Documentation/CorePackages/Publishing.md#frequently-asked-questions), this document can be updated incase of new errors. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CV3%20Publishing%5Cone-pager.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CV3%20Publishing%5Cone-pager.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CV3%20Publishing%5Cone-pager.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/vestigial-objects-onepager.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/vestigial-objects-onepager.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/vestigial-objects-onepager.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/vestigial-objects-onepager.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,122 +0,0 @@ -# "Vestigial objects" one-pager - -Parent epic: [Regularly find and delete vestigial objects in .NET Engineering Services Subscriptions](https://github.com/dotnet/arcade/issues/8814) - -The goal of this effort is to develop a consistent, approachable method to understand the existing Azure inventory - -This effort must answer these specific questions: - -- *What resources do we have now?* (Or, what resources exist that are not related to running our services.) -- *What resources have changed recently?* (Or, Something has broken, have there been any recent configuration changes?) - -## Stakeholders - -- Operations lead -- First Responder lead - -It is expected the primary audience of this effort are First Responders, for service failure diagnosis, and Leadership, for cost analysis. - -## Risks - -Risk is low. - -All data already exists in Azure, and Azure itself provides almost all of the infrastructure necessary for use. The goals of this effort are supported and expected use of this data. - -All Azure data stores involved are Kusto-like, which is a well-understood technology in dnceng. - -This effort will also use Grafana and Power BI, which are well-established tools in dnceng. - -## The Plan - -Azure provides infrastructure and features we can leverage to achieve our goals. - -- [Azure Resource Graph](https://learn.microsoft.com/en-us/azure/governance/resource-graph/overview) -- [Event Logs](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log) and [Resource Logs](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/resource-logs) -- [Azure Monitor](https://learn.microsoft.com/en-us/azure/azure-monitor/overview) and Log Analytics -- [Resource Tags](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources) - -The Resource Graph, Event Logs, and Resource Logs are stores of audit and activity data generated by Azure. In some cases, like the Resource Graph, this information is already generated and available directly for query. In other cases, like specific resource auditing, logs must be enabled by a resource owner. Generally, this uses Azure Monitor and is listed as the "Export Log" feature. - -### Understanding current inventory - -The Azure Resource Graph provides a Kusto-like query interface to all resources in a subscription. - -Azure allows "Tags", which are simply key-value pairs, to be arbitrarily associated with almost any resource. Internally, Microsoft already uses this feature to track assets and configuration across subscriptions (e.g., AzSecPack, NRMSException). "Inventory" is an explicit use case supported in Azure. - -This effort will develop a set of standard Tags that will be applied to resources. These Tags will identify, at minimum, resources that are necessary for operation of our services. Application of tags should automated where possible. Resources deployed by ARM or as part of the weekly deployment, for example, will have their deployment process modified to attach these tags with each deployment. - -The inverse is also interesting: It will highlight resources that are _not_ necessary for the operation of our services. These will be analyzed and their purpose understood, creating more operational Tags as needed. They may also be deleted to eliminate unnecessary spending. - -### Understanding changes to inventory - -While the Resource Graph provides a (mostly) static view of resources, the Event and Resource Logs provide a view of changes to a resource. - -The specific Log information recorded varies from resource to resource, and a resource can generally be configured to log at different level of details. Coarse logging is enabled at the Subscription level. More detailed, resource-specific logging (for example, which specific secrets are accessed from a Key Vault) may be enabled at the resource level. - -This effort shall ensure that, at minimum, creation and deletion of Resources are logged. Additionally, an appropriate level of configuration change shall be captured. This should be at least include the existance of a change and who made that change, but may include more detailed as desired. We may adjust the level of detail over time, though likely any change will need to be decided deliberately (Changes and other detailed activity can be verbose and are generally partitioned in Resource configurations to manage bandwidth use). - -Records between Resource Graph and Event Log may be joined using a correlation ID provided for this purpose. - -Once inventory Tags are stable, this data will make plain any new resources created, when they were created and who created them. - -## Tasks - -- [ ] Define tags schema - - [ ] Indicating that an asset is an operational asset - - [ ] Consider deployment information -- [ ] Modify service deployments to publish inventory tags with deployed resources -- [ ] Manually add inventory tags to resources not automatically deployed -- [ ] Configure subscriptions to export to a designated Log Analytics Workspace - - [ ] Understand implications of Azure datacenter location and ingestion costs - - [ ] Evaluate the possibility of using deployments to enforce audit configuration - - [ ] Evaluate including Key Vault auditing information -- [ ] Develop Dashboards presenting data to expected audience. Consider Grafana and Power BI (or a mix of both). - - [ ] FR can quickly view changes to an arbitrary resource - - [ ] New resources or resources with an unknown purpose are identifiable from a dashboard -- [ ] Develop documentation - - [ ] On use, targeting expected audience. Where to find information. How to interpret dashboards. Basic, pertinent information on how to interpret the raw Azure data. How to develop and run custom audit-like queries. - - [ ] For new services, to ensure new resoures are properly inventoried and audited - -## Ideas discarded - -Adopt Azure deployment technologies like ARM, Bicep. - -Pros: - -- Supports configuration as code -- Explicit and absolute control over all entities and their configuration within a resource group -- Re-deployment overwrites any changes back to their expected state - -Cons: - -- Has no existing pattern within dnceng -- Would require significant effort initially establishing configuration -- Not be aligned with team's current operational goals - -## Telemetry and Monitoring - -This effort is itself telemetry and monitoring. Almost all data exists in and is managed by Azure. There is nothing intrinsic to monitor and thus requires no alerting. - -## FR Handoff - -FR is expected to be a critical user of audit information (specifcally, "recent resource changes"). Their acceptance of the final product is required. - -## Appendix: Resources - -[Azure resource inventory helps manage operational efficiency and compliance](https://www.microsoft.com/en-us/insidetrack/azure-resource-inventory-helps-manage-operational-efficiency-and-compliance) - -[Azure security logging and auditing](https://docs.microsoft.com/en-us/azure/security/fundamentals/log-audit) - -[Azure Activity Log event schema](https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log-schema) - -[Use tags to organize your Azure resources and management hierarchy](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources) - -[Explore your Azure resources with Resource Graph](https://learn.microsoft.com/en-us/azure/governance/resource-graph/concepts/explore-resources) - -[Query changes made to resource properties](https://learn.microsoft.com/en-us/azure/governance/resource-graph/how-to/get-resource-changes) - -[Azure Resource Graph table and resource type reference](https://learn.microsoft.com/en-us/azure/governance/resource-graph/reference/supported-tables-resources) - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5Cvestigial-objects-onepager.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5Cvestigial-objects-onepager.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5Cvestigial-objects-onepager.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/VSTS/dotnet-bot-github-service-endpoint.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/VSTS/dotnet-bot-github-service-endpoint.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/VSTS/dotnet-bot-github-service-endpoint.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/VSTS/dotnet-bot-github-service-endpoint.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -# DotNet-Bot GitHub Service Endpoint - -## Public project - -See the [onboarding documentation](https://github.com/dotnet/arcade/blob/main/Documentation/AzureDevOps/AzureDevOpsOnboarding.md#github-connections) - -## Internal project - -### Internal VSTS service endpoint - -The VSTS service endpoint used for communication with GitHub (handles syncing source, setting up web hooks, etc...) is named `DotNet-Bot GitHub Internal Connection`. The PAT scopes used for the service endpoint are slightly different than what is used for the Public project. It should be available for GitHub connections in the [internal project](https://dnceng.visualstudio.com/internal). - -Service endpoint name: DotNet-Bot GitHub Internal Connection - -### Internal GitHub service account - -The `DotNet-Bot GitHub Internal Connection` makes use of the `dotnet-bot` service account. Repo's using the `DotNet-Bot GitHub Internal Connection` must make `dotnet-bot` a [collaborator](https://help.github.com/articles/permission-levels-for-a-user-account-repository/#collaborator-access-on-a-repository-owned-by-a-user-account) (Admin access) on their GitHub repo. - -### Internal personal authentication token - -The service endpoint uses a PAT with **`repo`**, `user`, and `admin:repo_hook` permissions generated by the `dotnet-vsts-github-b` account. You can find the PAT in `EngKeyVault` in the `dotnet-vsts-github-b-user-repo-adminrepohook-pat` secret. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CVSTS%5Cdotnet-bot-github-service-endpoint.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CVSTS%5Cdotnet-bot-github-service-endpoint.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CVSTS%5Cdotnet-bot-github-service-endpoint.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/VSTS/helix-job-sender.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/VSTS/helix-job-sender.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/VSTS/helix-job-sender.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/VSTS/helix-job-sender.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,52 +0,0 @@ -# Arcade Helix Job Sender Plan - -## The Current State of Things in Arcade - -Currently, to send a job to Helix using Arcade, developers need to do the following: -* Add the `Microsoft.DotNet.Helix.Sdk` package to their `global.json` -* Send up `scriptrunner.py` as part of a correlation payload if they want to receive XUnit results -* Create an MSBuild proj file which specifies source, type, build number, queue, and appropriate payloads (both correlation & work item). This is capable of handling single-directory wildcards (e.g. zip up all the directories in directory /xyz). -* Doing some trickery to output the correlation ID from that project file to an environment variable that can be read by future tasks -* Create a custom script file that monitors Helix for work item completion and reports whether tests succeeded or failed. -* Add a variable group to their build which contains the appropriate Helix token secret - -Then, during their build they must: -* Do some work to prep all their test payloads into individual folders. -* Call `dotnet msbuild` on their MSBuild proj file (regular MSBuild won't work) -* If they want to send the same items to multiple queues, they must repeatedly call the same proj file while changing the `HelixTargetQueue` variable -* Trigger their script to wait for Helix to finish - -## Where We Want to Go - -While devs will still need to prepare their tests to send to Helix, we can improve this experience for them. To do this, we will be creating an MSBuild task included in Arcade's `eng/common` directory. This would all be wrapped in a YAML template for easy inclusion in CI builds. - -The current thinking for the future process is as follows: - -1. Devs take a dependency on Arcade and the `Microsoft.DotNet.Helix.Sdk` -2. Add a variable group to their build which contains the appropriate Helix token secret -3. Devs can choose to either use a YAML template or an MSBuild task (both located in Arcade's `eng/common` directory) to talk to Helix. The YAML template is simply a wrapper for the MSBuild task. -4. If they choose to use the YAML template, they provide the source, type, queues, and payloads as parameters to the template. One of the template parameters would allow for specifying pre- and post-task scripts to run, which would incorporate the functionality of `scriptrunner.py` today. -5. The MSBuild task would support multiqueueing and waiting on multiple jobs. -6. The task would then wait for the job to finish. Optional parameters would be provided for whether failed tests should fail the build or not and whether it should simply fire and forget. - -The YAML template is not top priority as devs are familiar with MSBuild tasks. However, having a template would be nice as it provides consistency with YAML. - -## Work to be Done - -1. Add .zip file payload support to the SDK (assigned to jofortes; estimated 1 hour of work): PR [here](https://github.com/dotnet/arcade/pull/766) -2. Add multiqueueing support to the MSBuild task using MSBuild batching (assigned to jofortes; estimated 1 day work): PR [here](https://github.com/dotnet/arcade/pull/768) -3. Add multi-job waiting to the SDK and link it to MSBuild task (assigned to jofortes; estimated 2 days work) -4. Add pre- and post-task scripting functionality to the SDK and support XUnit result reporting (assigned to alperovi; estimated 2 days of work): PR [here](https://github.com/dotnet/arcade/pull/767/files) -5. Documentation for use of all this jazz (assigned to jofortes; estimated 1 day of work) -6. (Stretch goal) Add YAML template wrapper to MSBuild task (assigned to jofortes; estimated 1 day of work) - -#### Completion Schedule: - -* By end-of-day Fri Sep 14: Items 1 & 2 completed; item 3 in progress -* By end-of-day Wed Sep 19: Items 3 & 4 completed; item 5 in progress -* By end-of-day Fri Sep 21: Item 5 completed; Item 6 hopefully completed; in-progress if not - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CVSTS%5Chelix-job-sender.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CVSTS%5Chelix-job-sender.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CVSTS%5Chelix-job-sender.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/VSTS/pipebuild-feature-history.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/VSTS/pipebuild-feature-history.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/VSTS/pipebuild-feature-history.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/VSTS/pipebuild-feature-history.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,169 +0,0 @@ -# PipeBuild History - -## Background - -PipeBuild was created out of the need to produce a single unified build (set of binaries) which spanned multiple OS's and architectures. In some instances, architecture was less of a motivating factor, and configuration certainly wasn't a factor; because those aspects could have been done on a single machine or independently in the case of configuration. The ability to parallelize all of these aspects, however, proved tremendously valuable in terms of throughput and certainly leads to more desirable architectural patterns. - -Here's the "evolution" of PipeBuild broken into loosely chronologically implemented features. - -### Feature 1 - Creating an Orchestrator -Initially, PipeBuild was very simple. There was a json file which defined build definitions, and one that combined build definitions into pipelines. Being "aware" of a build definition meant associating a name with a VSTS Build Definition ID. - -Definitions.json - -``` - "Definitions": [ - { - "DefinitionId": 893, - "Name": "CoreFx-Windows-Trusted", - "ProjectName": "DevDiv", - "BaseUrl": "https://devdiv.visualstudio.com/DefaultCollection" - }, - { - "DefinitionId": 1054, - "Name": "CoreFx-Linux-Native-Trusted", - "ProjectName": "DevDiv", - "BaseUrl": "https://devdiv.visualstudio.com/DefaultCollection" - } - ] -``` - -Pipelines.json -``` - "Pipelines": [ - { - "Name": "Trusted-All-Release", - "Parameters": { - "TreatWarningsAsErrors": "false" - }, - "Definitions": [ - { - "Name": "CoreFx-Linux-Native-Trusted", - "Parameters": { - "DockerTag": "debian82_prereqs_2", - "ConfigurationGroup": "Release" - } - }, - { - "Name": "CoreFx-Linux-Native-Trusted", - "Parameters": { - "DockerTag": "rhel7_prereqs_2", - "ConfigurationGroup": "Release" - } - }, - ... -``` - -We could then define a named definition, and provide values for variables defined in that definition (via the defined pipeline). When PipeBuild was launched, it would update replace any named variables in the definition with the values in the pipeline.json file. Side note: the pipeline.json and definitions.json file all lived in source code next to PipeBuild itself (this very quickly proved to be an unwieldy model). I'll outline some of the immediate pitfalls, but it was a very quickly thrown together solution to a major problem we had so problems were expected. - -Cons: - -- pipeline definitions next to PipeBuild source code and not in product repos meant a multi-step process for making changes to the build and a synchronization nightmare. It also meant that we started seeing a proliferation of checked in files named "pipelines.json", "pipelines.corefx.json", "pipelines.corefx.1.0.0.json", "pipelines.corefx.1.1.0.json", etc... Additionally, any change to the pipelines ended up being handled by the same person (not a product repo dev but a removed engineering dev) because the process was so disjoint from the source code. -- We ended up duplicating a lot of data because you would have one pipeline for Release, and one for Debug, and you defined them in the same file but separately. We later made a change to support definition groups so that you could re-use the same set of definitions for a pipeline but provide different variable values to it (Debug vs Release). This cut down the duplication and the (somewhat common) instance where someone would make a change to the Release build but not scroll down through the massive file and make the exact same change to the Debug pipeline. -- Reliability wasn't great, but it's actually gotten worse since then as our requirements have grown -- Growing pains. As PipeBuild became adopted across more and more teams, support and maintenance costs skyrocketed - -Pros: - -- Note the reference to "DockerTag" in the pipeline. This reliance on Docker ended up being a huge win in terms of machine maintenance since we could define one VM image but run our builds / tests on many Linux distros. -- At the time DotNet Core was moving towards JSON, so the format was familiar and somewhat reasonable by devs. -- It did the basic job we wanted; ran pipeline jobs in parallel or sequentially, across platforms - -### Feature 2 - Definition groups - -As mentioned in the cons section of "Phase 1", our primitive json model meant for much duplication when defining a pipeline. The more places a dev has to change code, the more chance of failure. Making pipeline changes generally required a few cycles of: update, run an official build, investigate failures, rinse, and repeat. That cycle could be about 1 - 2 hours depending on the repo. The result was that making any change to the pipeline requied at least a day to get right. - -We introduced the concept of "Definition groups" which allowed us to define a set of build definitions with default variable values. The definition group could be explicitly referenced with a different variable value which applied to the entire grouping. This was our solution which reduced duplicated code and reduced dev errors considerably. It should be noted that this (like many of the "solutions") was not necessarily the best solution, but it addressed a certain need, and was the kind of thingyou do when there is limited investment towards a tool. The major take-away, is eliminating the places where code needs to be duplicated is a HUGE win. - -### Feature 3 - Reporting Parameters - -Reporting parameters had some positive characteristics and some negative ones. The issue we encountered was that VSTS is keenly adapted towards builds and less (or not at all) tailored towards tests. Some subset of our tests (public unit tests) were a part of our product builds, but to fully test our product across all of the required architectures, we had to create a different thing called Helix. Helix allowed us to take our product build pieces along with our test binaries, and massively parallelize running tests across architectures / platforms /etc. VSTS didn't have a great way to view both our builds and all of our tests in one place, so a new system (Mission Control) was developed that could collate all of the data into one viewable place. - -The way data was exposed from VSTS to Mission Control was via Reporting Parameters. Reporting Parameters were variables that our Orchestrator exposed from VSTS to Mission Control so that Mission Control could better identify builds (things like build number, platform, configuration, architecture, url, etc...). - -Positives: -- The data allowed us to provide direct links from Mission Control to VSTS builds with differentiated labels. -- The data allowed us to group product builds together and also link test results (from Helix) with those builds. Ok, to be fair, some of this was the Reporting Parameters, and some of it was other telemetry that we wired into our orchestrator (like providing a unique guid identifier unifying every product build, test build, and test run to a single orchestrated build instance). - -Negatives: -- I hated Reporting Parameters. It was always an as-needed thing. As our supported build variations increased, we would have to go back and plumb through an update to support the new requirements. ie, all of a sudden we would need to differentiate what was displayed in Mission Control based on configuration or architecture or some other factor. I hated this because it meant that the Product team would expose the requirement to the Mission Control team. The Mission Control team would make the update to their source to support the new parameter, then they would talk to the PipeBuild team (not really a team), and that person would have to go change the pipeline json to support the new parameter. It was terribly inefficient and annoying, and the requirements varied across repos. - -### Feature 4 - SkipBranchAndVersionOverrides - -When developing a pipeline, we found that there were some build legs that weren't necessarily associated with a repo. This was an intentional thing for our "Publish" build definition which we shared across repos. We didn't want to arbitrarily force that build leg to clone / sync the repo which the build legs were using because every clone is another possible point of failure and we, also, were beginning to focus on performance (builds were slow). Cutting out an additional 20 minute clone / sync step (at the time our repos were not ideally performant) was a huge win. The simple fix was to add a switch ("SkipBranchAndVersionOverrides") that told the build definition to ignore any repo / branch information the orchstrator was providing. - -Take-aways: -- Sharing build definitions is a thing and provides many benefits. These are the same benefits which are widely discussed by any code-reuse proponents -- Performance is important -- Reliability is important -- Most of these take-aways are pretty obvious, but the less obvious one (sharing build definition), shouldn't be overlooked. - -### Feature 5 - Cleanup - -As we continued to grow in the number of repos that we built and docker based linux variants we supported, we started to see build failures on a regular (~ every 3 weeks builds would start to fail across the board) because machine disk space would fill up and VSTS didn't provide the level of cleanup that we required when dealing with hundreds of builds a day in a fixed machine pool shared across products. - -To address disk space issues we invested in infrastructure that we could run on every build to cleanup the VSTS agent working directories which contained builds older than a day, and also to cleanup docker images / containers which began accumulating on machines and taking up precious space. The compromise of only deleting day old builds arose because we couldn't cleanup every build (though we wanted to) due to repos failing but holding locked processes that would cause cleanup to fail. The workaround of ignoring those cases wasn't acceptable because it meant that every build we ran reported the same warning of a failed cleanup task and eternally "yellow" builds is not as pleasant as seeing "green" builds. - -Cleanup is actually a major thing as every time a machines disk drive fills, it requires someone to investigate the issue then manually clean it up or enlist the help of DDFUN. It's even worse because machines (obviously) fill up disk space faster under heavy usage which tends to happen when products are preparing to ship and you really don't want to see random infrastructure failures. - -### Feature 6 - Checked in definitions - -The number of repos and branches (release branches, servicing branches, dev branches) that we started to support for official builds was continually growing. It quickly became clear that supporting a branching code base is an increasingly difficult task. For example: If we had to make a change to the Linux build definitions for a release, we had to go manually update about 15 build definitions. If there was a breaking change to our orchestrator, the number of build definitions that had to be updated was closer to 40 (at the time...). VSTS was slow and this was an extremely time consuming and error prone process. - -The more difficult task with definitions, was that, when we would branch for a release, we had to clone all of our build definitions, rename them, update our definitions.json and pipeline.json files, and validate. This was a terrible ordeal. - -At some point, we discovered that, via REST API's, we could download the build definition json and put it in our product source repos as code. Moving to this method was a HUGE win when we would branch for a release. It should be noted that we didn't branch our orchestrator along with servicing branches, or the VSTS build definition which ran the PipeBuild orchestrator. We always thought we were going to move away from PipeBuild, so we've lived with this state, but if I had to push for one more feature to add to our current PipeBuild tool, it would certainly be to add both of those pieces as code. - -One other minor win we had with checked in definitions, was associating the build definition ID of the orchestrating PipeBuild VSTS definition with the orchestrated build legs VSTS build. Prior to this minor change, it was an engineering feat to determine which of the various PipeBuild definitions representing various servicing releases had scheduled a particular build leg. - -Checked in definitions are clearly superior for DotNet teams because of their branching nature, but there are certainly some downsides to our current iteration -- We never invested in a clean flow for modifying checked in definitions. The JSON dump style works, but it includes a bunch of extra data which devs don't care about, or understand. I wrote a tool that launches a web browser and loads the local JSON into a new build definition so that it can be iterated on via the standard VSTS definition editing process. After making changes via the web UI, you can use the same tool to download the code again locally. The tool was never truly invested in though, and often the code you downloaded from VSTS would look vastly different because of back-end formatting changes which would add additional metadata or change GUIDs (devs never understood Task GUIDs). -- The JSON build definitions contained too much extra metadata and it was nearly impossible for any dev to manually make a change to the definition with a text editor unless it was changing a variable name / value. -- Secrets became confusing because they couldn't be defined by the JSON, they were provided by the PipeBuild orchestrator. - -### Feature 7 - Conditional build legs - -I'll be brief on this topic because VSTS now provides custom conditions. Prior to custom conditions, we had to implement a feature that would skip an entire build leg (we didn't have step / task level access) for some publishing scenarios. Yay custom conditions! - -### Feature 8 - Azure Key Vault - -Secret management started to become more and more of an issue. Whether it was a PAT expiring or (and I'm guilty of this on one occassion) an unintended secret getting leaked, when you had to update a secret in VSTS, it became a nightmare. - -- Multiple product teams owning their own builds meant that the same secret was defined by differently named variables in different repos -- Multiple devs working on builds meant that sometimes secrets were unintentionally duplicated with different variables -- You can't read the secrets (duh), so, without careful monitoring, it became nearly impossible to know which variable applied to which secret. - -Secrets were defined all over the place! Updating them was a nightmare. Thankfully, we were following the practice of keeping our secrets in Azure Key Vault so that we could retrieve them if necessary; for example, when bringing up a new orchestrated build. There was still no connection between key vault and the VSTS build definition, so if we had to update a secret it fell on one of two or three devs that were familiar with the system to go through and manually update all of the numerous build definitions. - -Our solution was to build in azure key vault access to our build orchestrator. Rather than keeping secrets in VSTS build definitions, we kept plain text values like this... - -``` -[AzureKeyVault=EngKeyVault,SecretName=dn-bot-devdiv-build-rw-code-rw] -``` - -When PipeBuild saw a variable with a text value looking similar to the above, it would connect to Azure Key Vault and retrieve the specified secret from the specified vault. After this change, there was only one secret (the access Azure Key Vault secret) in every PipeBuild VSTS definition, and all of the rest of the secrets were plain-text encoded values. Devs could reason about what a secret was, and where to find its value. Cycling a secret now meant updating the value in Azure Key Vault and every build definition would just continue to work. - -On the down side, individual build legs still have no connection to the azure secret values, they just have null values in the JSON marked as secret. So, you still have to know to go look at the PipeBuild VSTS definition to figure what the secret value is. - -### Things we never solved - -As a reminder, the .NET Core Engineering team never wanted to own a build orchestrator. It was a product which was created to fill a need which VSTS (and other solutions) weren't providing. The tool worked (with some pain along the way), but since it was always viewed as a temporary solution, it never was fully funded or treated with the intent of being a top notch piece of infrastructure. That mindset meant that many features which would have made our lives much easier, were added late, or never implemented. Here is an additional list of some of the features that we always wanted to implement but never got funded. - -- PipeBuild definition not checked in. The PipeBuild orchestrator is a tool that is launched via a VSTS build definition. While individual build legs are defined by checked in JSON code, the orchestrating definition is not. PipeBuild does not fork with the code and neither does the launching VSTS build definition. - -- PipeBuild was never versioned. Every official build in every branch of every product, uses the same code base (HEAD). That meant breaking changes were not possible though they happened on occassion at great expense. Some times a breaking change wouldn't be discovered to have occurred until a dormant servicing branch was spun up to produce a servicing fix. The cost at that point is prohibitive both because spinning builds in a servicing branch can be difficult, and because knowledge of how that branch worked could be lost. - -- Clean PipeBuild output. Our PipeBuild output just periodically queries VSTS for build status and dumps the output to the UI console. Tracking down a failing build leg while the build was in progress was difficult as its status would just scroll off the screen if you weren't diligent. If you waited until the build completed, you would get a dump of the failing build legs, but you had to scroll to the bottom of a lengthy output and doing this on a mobile device was an exercisein extreme patience. - -- Dev workflow for modifying definitions. Checked in definitions were great, but never fully supported after implementation. It was difficult to reason about the JSON defintions, and editing them required every dev to ping me for access to the hacky tool I had written and mentioned above. Merging two JSON blobs representing different VSTS api versions was very arduous. - -- Test changes to PipeBuild code path. To this day, there is no great way to test changes to our official builds without actually merging those changes and scheduling an official build. The current "best" work around, is to clone a definition, disable official build logging, change the title, and figure out some way to disable publishing (remove the publishing leg, change the publishing endpoint, or change the build number format to ensure there is no package version conflict when publishing to MyGet). - -- Shared Libraries for build orchestrator. For good and bad reasons, shared infrastructure libraries are currently available via DotNet BuildTools. BuildTools provides some benefits, but there are a lot of current concerns over the tooling. Discussing the concerns with BuildTools is an entirely different effort. - -- Replayability. Reliability has been (and continues to be) one of the most outstanding (not in a good way) issues that we have with respect to builds. There has been a tremendous effort to invest in our infrastructure so that it is resilient to intermittent network issues. In fact, we have been so busy fighting intermittent network issues, machine issues, disk space issues, etc... that we have not had time to invest in recovering from failures. We create a lot of builds for a single orchestrated build, and failures occur regularly. Today, we're forced to hotfix the failure (if possible) and then respin an entire build. This means that any failure requires at least a two hour reset to attempt to get a clean build. Multiplying that single repo concept across an entire orchestrated product (requiring many repos to successfully build) can lead to a nearly impossible task of getting 6 repos to build cleanly and understanding that any repo failing will likely lead to a day delayed trying to get another build. Re-entrant orchestration where one failing repo could be replayed without resetting an entire orchestrated build would be monumental. Barring that, even having replayability so that a single repo is not forced to entirely reset would provide benefit. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CVSTS%5Cpipebuild-feature-history.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CVSTS%5Cpipebuild-feature-history.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CVSTS%5Cpipebuild-feature-history.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/VSTS/signed-dnceng.visualstudio.com-builds.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/VSTS/signed-dnceng.visualstudio.com-builds.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/VSTS/signed-dnceng.visualstudio.com-builds.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/VSTS/signed-dnceng.visualstudio.com-builds.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,24 +0,0 @@ -# Signed dnceng.visualstudio.com builds - -Dnceng.visualstudio.com does not have support for signed builds. - -Code should still be mirrored to dnceng.visualstudio.com/internal as outlined in the [Azure DevOps Guidance](https://github.com/dotnet/arcade/blob/main/Documentation/AzureDevOps/VSTSGuidance.md#projects). - -## Task based build definitions - -If your build definition is task based, then the build definition for signing should be created in devdiv.visualstudio.com with an "External Git" source which references the dnceng.visualstudio.com/internal git repository - -1. Select a source: External Git -2. Change the Connection to "New Service Endpoint" - - User name: dotnet-bot@microsoft.com - - Password / Token Key: Listed in "EngKeyVault" as "dn-bot-dnceng-build-rw-code-rw" - - You can get Read access to "EngKeyVault" by joining the "DncEngKvRead" [security group](https://idweb/identitymanagement/aspx/groups/AllGroups.aspx) - - Note: It may take a few hours for permissions to propagate - -## Yaml based build definitions - -If your build definition is yaml based, then the build definition for signing should be created in devdiv.visualstudio.com, but your code should **also be mirrored into devdiv.visualstudio.com** and the DevDiv Git source should be used for building. Yaml is only supported for source code from the same project or from specific providers (like GitHub), it is not supported for source code from an external Git source (or other project collection). - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CVSTS%5Csigned-dnceng.visualstudio.com-builds.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CVSTS%5Csigned-dnceng.visualstudio.com-builds.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CVSTS%5Csigned-dnceng.visualstudio.com-builds.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/VSTS/vsts-preview-versus-pipebuild.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/VSTS/vsts-preview-versus-pipebuild.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/VSTS/vsts-preview-versus-pipebuild.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/VSTS/vsts-preview-versus-pipebuild.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,79 +0,0 @@ -I sat down with the intent of prototyping an orchestrated build of a single repo using the new VSTS "YAML definitions" preview feature. When I started writing this doc, I intended to just cover my experience, but I realized that it's difficult to understand why some things bothered me without giving some background about where our build orchestration efforts began and where they are now. Given the early stage of the feature, it was obvious that I would encounter some hiccups, and it wasn't clear just how close to a working prototype I would get. What follows is a high level overview of my initial reaction to the VSTS Preview Features in their current state and then my notes related to the YAML definitions and how that experience deviates from our current orchestrated build solution (PipeBuild). For additional context, there's a companion document I wrote which covers a brief [history of PipeBuild](pipebuild-feature-history.md) and the more impactful features we added to it as our needs evolved. - -For context, when I say, the "YAML definitions" preview feature, I'm including the VSTS features which permit configuration as code (different than checked in build definitions), and the VSTS implementation of a Pipeline (orchestrated repo build). From my limited experience, there is no current "Product orchestration" feature which would support orchestrating multiple repository spanning builds. You could certainly design something within the current feature set to support product orchestration, but it's not a fully supported first-class feature and it would be a bit of an unmanageable mess. - -# VSTS Preview Features - -Before I dive into my experience, I wanted to briefly cover the involved VSTS Preview Features and my initial take on them. - -## Config as Code - -"Config as code" is not the same as a "checked in build definition". In PipeBuild, we take all of the json that defines a VSTS build definition, download it, and check it in to source. In "YAML definitions", you take only the "configuration" parts that define a build definition and check them in as code. I think this abstraction is positive. The abstraction allows us to store just the data we care about and want to manipulate. There's a lot of pieces to a "checked in build definition" that we store that we just don't care about (or understand). On the server side, they have meaning, but to us it's just ignored data which gets in the way when we're trying to understand the data we're looking at and how we want to change it. It also means, I would hope, that any changes to build definitions would be seamless to "YAML definition" users. I think that, long-term, if we had decided to continue investing in PipeBuild, this is the direction we would have gone. It was always on our radar, as a next step, but checked in build definitions filled our immediate need and there was never a desire to further invest in PipeBuild. - -**Concerns:** -- One primary concern is what gaurantees we have that the API is not going to change and force us to make widespread updates to all of our various checked in "config as code" files. I don't see any way to specify a specific api version. At the moment, the API is in constant flux. Our previous history with VSTS has shown that this can be problematic if the agent API's change. Rolling forward should be an intentional /validated process (think Maestro PR's for dependencies but with API versions). - -## YAML - -I had no experience with YAML before playing around with "YAML definitions". It's not terribly difficult to pick up. There's less than an hour of documentation to read about the language specification, and then it's mostly understanding VSTS' schema implementation (which is fairly well documented though constantly changing). - -**Concerns:** -- Why another data format? We've built up a lot of infrastructure around JSON (mostly deprecated now), then MSBuild, and now YAML. I understand that MSBuild is not the perfect language format for many reasons, but history has shown it to be resilient and Microsoft (.NET Core) has decided to invest in that direction in spite of convincing arguments for other data languages (JSON). History has shown that we are stronger when staying within the Microsoft technology ecosystem. - -There are numerous examples where we tried to break away from Microsoft technology (for very valid reasons). .NET CLI put a large investment in using project.json's for package references then eventually transitioned to MSBuild. .NET Core moved to Jenkins for CI (and peripherally for official builds), but now is moving towards Microsoft VSTS. I'm certain that there are some counter-examples to this where moving away from our own products has been a long term win, like moving to GitHub for source control vs TFS. Though, even in that instance we are moving to Git technology but Microsoft hosted source control. - -Using yet another format means more investment, particularly in places where there is an intersection of data sharing between builds and code. I'd be curious to know what motivated YAML as the format of choice. - -## Pipelines - -The Pipeline functionality (scripted steps, parallel builds, matrix definitions) seems to be a reasonable implementation which covers much of the orchestration required for a single repo build. I have some feedback on current implementation details, but overall the model makes sense and its representation in the VSTS UI is fairly clean. It would be nice if phase dependencies were a bit more clearly defined. ie, currently, there is no way to visually determine which dependency is blocking a phase from running. - -**Concerns:** - -- It's not clear to me if product orchestration is intended to be a fully supported feature of VSTS or up to product teams to implement via Pipelines. If it is left to product teams, then the infrastructure investment on each team could be quite large given that I don't know how the current feature set would support re-entrant orchestration or assist in producing builds reliably (network failure retries). - -# PipeBuild development -For additional background and context, please see [pipebuild-feature-history.md](pipebuild-feature-history.md). Note that the linked doc does not specifically list features which VSTS doesn't support, it's just additional context. Some of the features mentioned there have already been solved by VSTS, others have not. - -# VSTS Prototyping Preview Features -Much of my experience is negatively skewed by the fact that the development work is still in progress. I do think that the experience I had, however, could prove valuable in moving the system from an effective system, to a slightly more user-friendly system. I don't intend to prescribe implementations here, only to cover my experience with VSTS Preview Features from the perspecitve of someone transitioning from PipeBuild. - -## Hello World -My initial step in creating a YAML definition was to create a simple "Hello World" YAML file. This was a pretty straight forward task and quickly accomplished. What I immediately realized though; was that, for more complext tasks, the model of coding locally, checking in, pushing to Git, and then scheduling a VSTS build to validate was not efficient. - -## Dev Loop -In considering how to improve my development loop, I stumbled upon the VSTS Agent source and the "--runLocal" option. This seemed like precisely what I needed to speed up my investigation. I installed an agent locally from the VSTS page, then I was able to test my YAML files on my dev box. Here, I made a couple of bad assumptions. Some of the features in the documentation didn't work, so I thought I assumed that the agent I downloaded from the page wasn't current. I cloned the agent source, built it, and updated the agent binaries I was using to Latest. - -> Note: I was pleasantly suprised to see that the build cmd file restored an sh.exe which was then used to pass build commands to the build sh file. The cool result was that the cmd file was just a wrapper around the sh file on Windows instead of duplicating a Windows and a Linux variation of the build script. - -Even with the latest code, the agent would complain about the schema of my YAML file and I made the bad assumption that the schema documentation was out of date and that the agent source code was the source of truth. I was able to attach a debugger to the agent and reason through the schema it expected so that I could produce a reasonably complicated YAML file. - -It turns out that the agent source code was not the source of truth, though neither was the documentation. Some version of the source code which was running on the server was the source of truth, the documentation was a close second (with a few days lag), and the agent was just a source of confusion. Regardless, it didn't stop me from making progress locally and I didn't realize this was an issue until the next day when I moved to testing on the server so that I could run jobs across platforms. Using the VSTS UI to validate schema changes in YAML is a horrific experience. - -## VSTS versus PipeBuild gaps -So, where does current VSTS functionality not quite close the gap to what we're accustomed to with PipeBuild? - -Here are some items that we must have in order to start using VSTS preview for our builds. - -- Azure Key Vault - currently not supported in the YAML schema I previewed though Chris Patterson has told me this is now suported via a task. - -- Templates - This feature was deprecated in the prototyping I was doing, but it would allow you to import YAML from another file into your pipeline. Support for this will definitely make for a cleaner (less error-prone) code-base. - -- Agent pools - Currently YAML definitions are only enabled in the Hosted agent pools. That means, we can't actually build our product because the machines in those pools don't have the necessary pre-requisites installed. - -- Build number format. It doesn't appear that there is (currently) a way to control the build number format via YAML. The documentation hints that this is on the radar very soon, https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted-features.md. Why does this matter to us? Currently, our PipeBuild VSTS definition will provide a specifically formatted build number which is then parsed by a library to produce an "OfficialBuildId". That OfficialBuildId is passed to every build leg so that generated binary and package version numbers are consistent for a build. An inability to control the build number format via YAML isn't necessarily a regression from what PipeBuild is doing today, but it does mean that we lose some of the benefit of config as code because anybody pointing at a YAML file will need to specifically know to go update their VSTS build definition to produce the proper build number format. - -- Telemetry - How do we report status to Mission Control? or elsewhere? - -These ae additional items which it would be great to utilize, but we could work around if they're not present - -- Docker - Docker is on the radar for support (https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/runtaskindocker.md), but I was unable to get this feature to work using the "Hosted Linux Agent" machine pool which supports YAML definitions. Having this as a supported task is fantastic, once it works... In the interim, we could implement functionality using similar semantics to how we build for docker today. - -- Tasks - At the moment, using any task, or understanding how to implement a task in your YAML schema is extremely difficult. Chris Patterson says that there is work in progress (shipping m126 [aka next week]) which will allow you to configure a task or definition in the UI and right-click to copy it to YAML. - -- Cleanup - I don't know if this is yet a priority or if we continue to use our infrastructure to clean agents / docker. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CVSTS%5Cvsts-preview-versus-pipebuild.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CVSTS%5Cvsts-preview-versus-pipebuild.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CVSTS%5Cvsts-preview-versus-pipebuild.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/VSTS/vsts-windows-connection-instructions.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/VSTS/vsts-windows-connection-instructions.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Project-Docs/VSTS/vsts-windows-connection-instructions.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Project-Docs/VSTS/vsts-windows-connection-instructions.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -# VSTS Windows Azure VM agent connection instructions - -1. Go to https://resources.azure.com/subscriptions/84a65c9a-787d-45da-b10a-3a1cefce8060/resourceGroups/dnceng-build-agents/providers/Microsoft.Network/loadBalancers/LB-helixage/inboundNatRules - -2. Machines are named as “helixage_[virtual machine #]” so look at the end of the `"name"` property which references the virtual machine # you care about. - - Example: - ```JSON - { - "name": "LoadBalancerBEAddressNatPool.0", - } - ``` - - This entry represents “helixage_0” - -3. In that item, find the "port #" from the `"value.properties.frontendPort"` value - -4. Connect to the machine: `mstsc /v: dnceng-helix.westus2.cloudapp.azure.com:[port #]` - - a. Username: dotnet-bot - - b. Password is available from **HelixProdKV** as *HelixVMAdminPassword* - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CProject-Docs%5CVSTS%5Cvsts-windows-connection-instructions.md)](https://helix.dot.net/f/p/5?p=Documentation%5CProject-Docs%5CVSTS%5Cvsts-windows-connection-instructions.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CProject-Docs%5CVSTS%5Cvsts-windows-connection-instructions.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/PublishConsumeContract.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/PublishConsumeContract.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/PublishConsumeContract.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/PublishConsumeContract.md 2023-11-13 13:20:34.000000000 +0000 @@ -53,7 +53,7 @@ ## Onboarding -Onboarding a repo to the toolset will be a [simple process](https://github.com/dotnet/arcade/tree/main/Documentation/Project-Docs/buildtools-bootstrap.md). +Onboarding a repo to the toolset will be a [simple process](https://github.com/dotnet/dnceng/tree/main/Documentation/ProjectDocs/buildtools-bootstrap.md). We will provide links to zips / tarballs to acquire the basic pieces necessary for bootstrapping the core tools SDK on a supported platform. @@ -106,7 +106,7 @@ - Utilities, exe's, scripts, etc which are part of the package functionality must be usable via MSBuild properties / targets. You should not have a collection of executable files in your package which do not include MSBuild entry points for using them. -- Additional package guidelines are outlined [here](https://github.com/dotnet/arcade/tree/main/Documentation/Project-Docs/Toolshed/TaskPackages.md#implementation-details) +- Additional package guidelines are outlined [here](https://github.com/dotnet/dnceng/tree/main/Documentation/ProjectDocs/Toolshed/TaskPackages.md#implementation-details) - Packages need to include accountability information in the nuspec. At a minimum, source repository link and commit SHA. diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Staging-Pipeline/making-and-validating-changes.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Staging-Pipeline/making-and-validating-changes.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Staging-Pipeline/making-and-validating-changes.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Staging-Pipeline/making-and-validating-changes.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,68 +0,0 @@ -# Making and Validating Changes to the Staging Pipeline - -Need to edit the staging pipeline for some reason? This doc has you covered. - -## Making Changes - -The staging pipeline ([Stage-DotNet](https://dev.azure.com/dnceng/internal/_build?definitionId=792)) is fairly easy to make changes to. -The pipeline has two main components: its [YAML](https://dev.azure.com/dnceng/internal/_git/dotnet-release?path=%2Feng%2Fpipeline&version=GBmain&_a=contents) and the [Release CLI/Library C# code](https://dev.azure.com/dnceng/internal/_git/dotnet-release?path=%2Fsrc%2FMicrosoft.DotNet.Release&version=GBmain&_a=contents). - -The Release CLI makes extensive use of dependency injection. -New changes may need to also modify [`ServiceCollectionExtensions.cs`](https://dev.azure.com/dnceng/internal/_git/dotnet-release?path=%2Fsrc%2FMicrosoft.DotNet.Release%2FMicrosoft.DotNet.ReleaseCli%2Fsrc%2FServiceCollectionExtensions.cs&version=GBmain) in order to have their dependency injection work properly. -Additionally, any new operations will need to be added to [`Program.cs`](https://dev.azure.com/dnceng/internal/_git/dotnet-release?path=%2Fsrc%2FMicrosoft.DotNet.Release%2FMicrosoft.DotNet.ReleaseCli%2Fsrc%2FProgram.cs&version=GBmain) in alphabetical order. - -The [Signing Extensions](https://dev.azure.com/dnceng/internal/_git/dotnet-release?path=%2Fsrc%2FMicrosoft.DotNet.Release%2FMicrosoft.DotNet.Signing.Extensions&version=GBmain) project is where all of our signing-related tasks live. If you need to make changes to the signing setup, modify this project. - -The Release CLI, Release Library, and Signing Extnesions already have tests alongside their code. -Any new functionality should have test coverage added to it. - -## Validating Changes - -So you've recently made a change to the pipeline and you want to make sure you're not going to break anything? -Good for you! Lucky for you, we (the authors of this document) have done some work to make that easier for you. - -### Stage-DotNet-Test - -This pipeline is your best friend when it comes to validating changes in the staging pipeline. -It has a separate entry point from Stage-DotNet ([`staging-test-pipeline.yml`](https://dev.azure.com/dnceng/internal/_git/dotnet-release?path=%2Fstaging-test-pipeline.yml&version=GBmain&_a=contents) vs. [`staging-pipeline.yml`](https://dev.azure.com/dnceng/internal/_git/dotnet-release?path=%2Fstaging-pipeline.yml&version=GBmain&_a=contents)), -but otherwise uses the exact same YAML templates and files past that entry point. -A few notable changes from the staging pipeline to the staging test pipeline: - -* Stage-DotNet-Test has a BAR ID prefilled for you – we've created a build that is known to work. -Note that when we say "work," we don't mean "entirely green"; you will still notice some orange circles in the validation steps. -However, all the pipeline functionality itself will work perfectly for you. -* Stage-DotNet-Test skips over approval stages – no need to babysit it! -* Stage-DotNet-Test tests publishing by actually publishing to temporarily created feeds and containers, -so any changes to publishing will actually be validated! - -Simply run the test pipeline on your branch and then wait six hours for it to finish and you'll be golden! - -### What? Six Hours? - -Okay, okay. If you have to iterate rapidly for some reason, -there is a way to save time and test only a particular stage or set of stages. - -1. Find a previous, successful run of the test pipeline. Copy the Build ID (the bit after `buildId=` in the URI). -2. Pull up the YAML file for the stage(s) you want to run. Add the following inputs to any `DownloadPipelineArtifact` tasks: -```yaml - source: 'specific' - project: '7ea9116e-9fac-403d-b258-b31fcf1bb293' - pipeline: 799 - preferTriggeringPipeline: true - runVersion: 'specific' - runId: the build ID copied from earlier - allowPartiallySucceededBuilds: true - allowFailedBuilds: true -``` -Note: if you want to use artifacts from Stage-DotNet instead of Stage-DotNet-Test, set `pipeline` to `792` instead. - -3. Open up [`eng/pipeline/stage_dotnet.yml`](https://dev.azure.com/dnceng/internal/_git/dotnet-release?path=%2Feng%2Fpipeline%2Fstage_dotnet.yml&version=GBmain&_a=contents) and comment out all of the stages prior to the one you're testing. -Then make sure to comment out the dependencies on those stages. -4. Running the pipeline now will skip the most time-consuming stages and go straight to the stage you want to test. - -Please still make sure to run the full test pipeline before checking in. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CStaging-Pipeline%5Cmaking-and-validating-changes.md)](https://helix.dot.net/f/p/5?p=Documentation%5CStaging-Pipeline%5Cmaking-and-validating-changes.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CStaging-Pipeline%5Cmaking-and-validating-changes.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Staging-Pipeline/running-the-pipeline.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Staging-Pipeline/running-the-pipeline.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Staging-Pipeline/running-the-pipeline.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Staging-Pipeline/running-the-pipeline.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ -# Running the Staging Pipeline - -Running DotNet-Stage may seem intimidating at first, but fear not! -It's only a little bit complicated. - -## Parameters - -The actual part you need to understand before running is the pipeline parameters. Here's a full description of them: - -1. **Bar Build Ids** – a comma-separated list of BAR IDs to use as the basis for the release. These should be installer BAR IDs. -2. **Treat build as...** – optionally override project as public or internal. If you know you want it to publish to public feeds/containers, you choose public; if you want to publish to internal feeds/containers, you choose internal. Normally, "default" should be selected. -3. **Security Release** – should be checked if this is a security release. -4. **Release Date** – corresponds to date of actual release (not today's date). -5. **CVE List** – if Security Release is checked, this should be filled out with the relevant CVEs fixed by the release. CVEs should be listed in the format `CVE--`. -6. **Certificate Substitutions** – not necessary in most cases. If you want to sign Windows files with a different certificate, you specify "{old certificate}={new certificate}" in this box to replace {old certificate} with {new certificate}. -7. **Always Download Asset List** – used for partial releases. Makes sure that we download that file which is required for releases. Leave as default for most cases. -8. **Skip Publish to vsufile** – only used for testing purposes. - -If you want to only run up to a certain stage, you can disable all later stages using the **Stages to run** feature of Azure Pipelines. - -## Failures - -There are several manual overrides for stages which are allowed to have failures – these stages require approval if the stage they're related to had any failures. For information on common causes of failure in these stages, check our [validation documentation](https://github.com/dotnet/arcade/blob/main/Documentation/Validation.md#what-do-i-do-if-an-issue-is-opened-in-my-repository). - -## Branching for Arcade Release - -Dotnet-Release should be branched on the same cadence as Arcade. Check the [Arcade Servicing doc](https://github.com/dotnet/arcade/blob/main/Documentation/Policy/ArcadeServicing.md) for more information on when branching occurs. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CStaging-Pipeline%5Crunning-the-pipeline.md)](https://helix.dot.net/f/p/5?p=Documentation%5CStaging-Pipeline%5Crunning-the-pipeline.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CStaging-Pipeline%5Crunning-the-pipeline.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/availability.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/availability.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/availability.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/availability.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,30 +0,0 @@ -# Hybrid Availability Expectations - -## Introduction -Please note that this guidance is West Coast centric as Fargo and Prague are coming up with their own guidelines. - -## Core Hours (e.g. hours that folks must be available): -Strictly speaking, there are no “core hours” as this goes against the spirit and intent of hybrid. However, see next sections on meetings and first responder duties. - -## Meeting Availability: -Meeting (remote and in person) attendance is expected from time to time. As per our async principles, a focus should be put on the value of any meetings scheduled so we can better respect the different time zones and hybrid work patterns. Regarding timing, meetings fall into three categories: -- Meetings which include the Prague team should generally be scheduled from 8:30am PST until 10am PST, and no later than 11am PST on exceptional cases. -- Meetings which include only folks from the continental US should generally be scheduled from 10am PST until 2pm PST. -- V-Team meetings have no set timeframe and is up to the v-team themselves. - -## Days in Office: -There is no expectation that there be a set schedule for days in the office as we’re emphasizing flexibility. - -Optional: Add your in-office days to your Teams' status - -## First Responders: -As a team we have a commitment to respond/help our customers during a certain time frame. Given this, it’s important that each dev is available while on FR rotation according to whatever is worked out by the FR lead. - -## Impact instead of Time: -Each of us should be focused primarily on delivering the right impact according to our role and level. The assumption is that it’ll likely take 40(ish) hours a week to have the right impact – but this priority order is important. The implication is that there should be flexibility such that we’re able to employ the rhythms that work best for us, leaving ample time/opportunity for family, personal health, well being, as well as work. - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cavailability.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cavailability.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cavailability.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/azdoworkitemguidance.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/azdoworkitemguidance.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/azdoworkitemguidance.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/azdoworkitemguidance.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -# Creating Azure Boards WorkItems - -For some issues, we will need to open Azure Boards workitems, rather than github issues. These include: - -* Security related changes (see [issue tracking guidance](IssueTrackingGuidance.md)) -* Any work for non-open source projects - -In these instances, we still want to have issues, and we still want to link them to Epics that are in arcade. - -## Opening an Azure Boards WorkItem - -* WorkItems should be opened in the [internal Azure DevOps project](https://dev.azure.com/dnceng/internal/_workitems/) -* When creating a new work item, create the work item as a [task](https://dev.azure.com/dnceng/internal/_workitems/create/Task) -* Set Area to internal\Dotnet-Core-Engineering -* Give it a meaningful title and description -* Update the GitHub Friendly Title and GitHub Friendly Description with information that can be shared on GitHub (in the public). -* Add a link to the GitHub epic in the Epic Issue field. -* After [#8567](https://github.com/dotnet/arcade/issues/8567) is complete, an issue linking your newly created Azure Boards work item will be created and added to the Projects (beta) board - * The epic issue field will be filled out if the GitHub link was supplied - * If the GitHub Friendly Title is set, the created github issue will use it. Otherwise, will use "Azure Boards Issue #[Issue Number]". - * If the GitHub Friendly Description is set, it will be used and a link to the Azure Boards work item will be added. Otherwise, the issue will only have a link to the Azure Boards work item. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cazdoworkitemguidance.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cazdoworkitemguidance.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cazdoworkitemguidance.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/BestPractices.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/BestPractices.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/BestPractices.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/BestPractices.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,24 +0,0 @@ -# Best Practices for .NET Engineering Services - -The intent of this document is to help the team learn and grow by sharing best practices that we have found. - -## Keeping Our Repos Healthy and Ready for Roll Out -- Anyone who checks in a change still needs to monitor the next main run, in any repo. - - PR validation is not the same as deployment to the staging environment and there will always be problems missed by PR validation unless we deploy an entire environment for every PR, which is not currently possible. - - The goal is to have a vendor monitoring our important pipelines - [Helix Machine Lifecycle Daily Process](https://dnceng.visualstudio.com/internal/_wiki/wikis/DNCEng%20Services%20Wiki/952/Helix-Machine-Lifecycle-Processes?anchor=daily%3A) - but everyone on the team should still make sure we are able roll out at any time. - - It’s a good principle to ask people to look at the next main run, it’s an even better one to not allow oneself to be broken for days at a time unnecessarily. -- Verify deployment and close issues you placed in the "waiting for rollout" column on our Project Board - - This is especially true for anything associated with grafana alerts. We may miss new alerts as they are concatinated to an existing issue. - - It is not the responsiblity of the individuals performing deployments to verify your issue is complete and closed out. -- The autoscaler is quite different from everything else in dotnet-helix-machines - - It is the only service within this repo and it causes us to duplicate any efforts involving Service Fabric changes - -## First Responder/Operations Work -- The Operations v-team - including our vendor resource - is responsible for triaging any internal work that come in from S360 and other internal notifications (i.e. emails from security, policy notifications from PM, etc). Any work that they determine as meeting the First Responder bar will be tagged for FR and be addressed by that virtual team. -- Current First Responder responsibilities, best practices and how to documentation can be found at our [Team Wiki](https://dev.azure.com/dnceng/internal/_wiki/wikis/DNCEng%20Services%20Wiki/889/Home) - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CDevGuide%5CBestPractices.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CDevGuide%5CBestPractices.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CDevGuide%5CBestPractices.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/documentationguidelines.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/documentationguidelines.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/documentationguidelines.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/documentationguidelines.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -# Documentation Guidelines - -All documentation should be added to the `dotnet/arcade` repository, [Documentation folder](https://github.com/dotnet/arcade/tree/main/Documentation). Please follow any procedures for adding new documentation to this folder as you would if you were adding the documentation to the soon-to-be-deprecated `dotnet/core-eng` repo. (For example, following the procedure in the One-Pager template regarding where project one-pagers should live.) - -**NOTICE**: Use your best judgement to exclude documentation that may have security implications. When in doubt, talk to the team or use placeholders in your pull requests to add documentation as not to accidentally expose sensitive information. - -If you have documentation that contains sensitive information that external-to-Microsoft folks should not have access to, then that documentation should be linked to from the public documentation and it should be placed in the Azure DevOps dnceng/internal wiki. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cdocumentationguidelines.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cdocumentationguidelines.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cdocumentationguidelines.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/happyhour.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/happyhour.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/happyhour.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/happyhour.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ -# Hybrid Happy Hours - -- Happy Hours should be both Teams and physical meetings. A nearby conference room should be booked for as many recurrences as possible. - - The meeting owner should have calendar reminders to make sure they book re-book the conference room for the next batch of recurrences every few months. -- Just like regular meetings, bringing laptops to Happy Hours is encouraged. This facilitates better face-to-face interaction with people who are participating on Teams. - - Coincidentally, it's also useful for Jackbox. -- Any games projected on the screen should also be screen-shared on Teams. - - Bring a capture card for console games (if possible; Jon owns one). - - Games should ideally allow remote participants to play as well; Jackbox games are a great example of this. However, it's totally okay to sometimes play something that's - not entirely remote-friendly (look, Jon really likes destroying everyone at Mario Kart), but even then, make sure it's screen-shared. - - If a game supports neither screen-sharing nor online multiplayer, it's best not to play it. -- Pop in and pop out at will – no need to feel like you have to stay for the full two hours! This applies to both virtual and in-person participants. - - Relatedly, feel free to continue working while attending – we love to have you there even if you can't participate in whatever games we're playing. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CDevGuide%5Chappyhour.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CDevGuide%5Chappyhour.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CDevGuide%5Chappyhour.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/hybridcollab.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/hybridcollab.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/hybridcollab.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/hybridcollab.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# Make Hybrid Work.....work - -**TL;DR:** Recognize that coming into work does not necessarily imply coding. Rather, coming into socialize and collaborate in person is very important in itself. - -As we experiment and experience the realities of hybrid, many of you are noticing what research is also showing – namely that solid work relationships are very important, and that hybrid/remote work can have a negative effect on those relationships. - -Given this, we agree to **designate one day a week where everyone in Redmond agrees to make best effort to come into the office for the express purpose of collaborating and socializing with the rest of the team.** - -**Agreement Specifics:** -- We will each do our reasonable best to be present each Thursday from 11am to 2pm for the express purpose of interacting with others, collaborating, chatting, and otherwise connecting - often informally - with our co-workers. -- No coding is expected during this time. (of course, it's alright to code too...) -- When on First Responder, taking care of our customers takes priority. -- It is understood that some are remote, and we should continue to be intentionally inclusive for those as well wherever and however possible. - -**Principles:** -1. Relationships are an important and necessary part of our work here at Microsoft and should be prioritized as such. -2. Relationships are easier to maintain and build on when there's consistent face-to-face interactions where possible. -3. Social/collaborative interactions with our co-workers should be prioritized as an important part of our job - e.g. work. - -**Rationale:** - -Research is showing more and more that social interactions are more of a "must have" than a "nice to have". In a recent write up from Microsoft Research ([Great Expectations: Making Hybrid Work Work](https://www.microsoft.com/en-us/worklab/work-trend-index/great-expectations-making-hybrid-work-work)), there are some interesting quotes in section 5 of the report. - -_One of the most felt aspects of remote and hybrid work is the impact it's had on our relationships. _[_Last year's Work Trend Index_](https://www.microsoft.com/en-us/worklab/work-trend-index/hybrid-work)_ revealed that teams became more siloed, and this year's study shows the trend one year later._ - -_When people trust one another and have [social] capital, you get a willingness to take risks, you get more innovation and creativity and less groupthink - Nancy Baym, Principal Researcher, Microsoft Research_ - -_When work-life balance is out of whack, most people cut out relationship-building for more urgent matters," says Constance Noonan Hadley, an organizational psychologist who studies workplace relationships. "Regardless of remote status, building relationships will still feel like a luxury workers cannot afford unless there is a shift in how time is prioritized and valued by managers_ - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CDevGuide%5Chybridcollab.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CDevGuide%5Chybridcollab.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CDevGuide%5Chybridcollab.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/hybridprinciples.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/hybridprinciples.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/hybridprinciples.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/hybridprinciples.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,38 +0,0 @@ -# Hybrid/Async Team Principles - -- Business goals are fully described, “done” is specified, and the path to completion articulated such that anyone from the working group can understand the purpose of the work with minimal clarification. - - Epics are how we’re currently doing this. -- The majority of communication done through “typed” (or formal) channels. - - Each v-team has its own Teams channel – which has the entire dnceng team on it. (this has been working well so far) - - Meeting notes and/or recordings are always taken and made widely available. -- It’s easily knowable what each dev is working on, status, and next steps. - - Stand ups are still an important team sync point. (For example, all work done is reflected on a team Kanban board) - - Each dev may also set weekly expectations. - - The specifics are more fully described in this dev guide. -- Our team’s “dev process” (how we work) is written down as a living document. - - How our team does what we do is codified in a "dev guide" (this set of md's in github) - which is constantly iterated on by devs themselves. - - The goal is that this document is actually useful day-to-day for the devs. This is key to keeping the document truly “alive” and relevant. - - As things change or are further clarified, the handbook is laid out well enough that it’s well known where to update it by anyone. -- Async discussions are generally (but not always) prioritized over synchronous. - - Devs are encouraged to question meetings by making sure there’s a clear agenda that would benefit by being conducted synchronously. - - Synchronous meetings are still *extremely* important. -- Intentional social interactions continue to be a top priority. - - We're continuing “happy hour” - - Periodic lunches and/or other events are scheduled every couple of months at a minimum -- Everything starts with a document. - - Proposal (Used to be meeting, but we need to move to a proposal first, then a meeting) - - Team Process (Used to be “ask a manager”, but we need to move to update the handbook) -- Each dev is a “manager of one”. - - Each dev is largely autonomous, consistently unblock themselves, and make things better for themselves and the entire team. -- Always iterating. - - Work is done is in the smallest chunks possible, then iterating. Velocity is more important than size of change. - - This thinking helps devs not get blocked on others. -- Intentional handoff - - When handing work to someone, make sure they have context - - Any investigation/existing work should be written down in the same place the handoff is happening to make the handoff productive - - Be clear why the handoff is happening (avoid mentions/forwards without indicating expectations or reasoning) - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CDevGuide%5Chybridprinciples.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CDevGuide%5Chybridprinciples.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CDevGuide%5Chybridprinciples.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/hybridtriage.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/hybridtriage.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/hybridtriage.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/hybridtriage.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,44 +0,0 @@ -# Hybrid Triage - -## Triage Logistics: -* One 30 minute synchronous meeting per week at each locale. In this case that’s one for Redmond, and a separate one for Prague. -* One triage driver per locale, although the driver could be shared or rotating. Triage driver responsibilities: - * Running CLI tool twice a week. Once the morning of the day synchronous triage occurs, and once 4 days later. - * Scheduling and running the weekly synchronous triage meeting, and keeping it on track and focused. - * Making sure stale issues are taken care of. -* It continues to be expected that issues are reviewed async (offline) by each senior dev at least once during the week. This should be made much easier using the new async tool – however, I’m well aware that there will be a strong propensity to just waiting until the weekly synchronous meeting. However, please remember the following: - * Seniors are expected to do things they don’t necessary like or are hard to do. - * 30 minutes is not enough time to get through all the issues if we don’t keep up during the week. - * The weekly meeting should be reserved for discussions about challenging or interesting issues – which is the thing that has been one of the things most missed. If we spend our time triaging boring issues….we won’t get that value. - * The added visibility has been super important, and the easiest way to solve this is to review the majority of issues throughout the week. -* Both locales should continue to review/triage all incoming issues. This is part of how we share context. - -## Scope: -For now, we will limit triage to core-eng and arcade issues. Depending on how things go, we can discuss bringing other repos in later. (e.g. xharness etc) - -## Tools: -* **Async Triage CLI** is a brand new tool that stuffs issue activity into the Async Triage Teams Channel. -* We also use **ZenHub** to manage our issues and epics. Please let your lead know if you have problems with ZenHub licenses or want to know more how to use the tool. (for example one super nice feature is the ability to manage issues across multiple repos) - -## Policy: -* Any dev can (and should) ask for further clarification as to what any issue is about from the creator. (any issue that does not get further clarification w/in 1 week is automatically closed) -* Any dev can triage (assign the issue to an epic) if they feel confident in doing so. -* All issues must be triaged within one week. -* Any issues that are > 1 week old, must be dealt with at the weekly synchronous meeting. - -## What triage is: (refresher) -* An issue is considered triaged when it’s assigned to a business priority. (we calls these Epics) -* The V-Team assigned to an epic is responsible for triaging/prioritizing the issues within their Epic – which may include “kicking” issues back out for triage again – e.g. unassigning them from an epic. -* Our current (now dated) documentation on Epics is here: https://dev.azure.com/dnceng/internal/_wiki/wikis/DNCEng%20Services%20Wiki/107/Sprint-Planning-and-Execution for a bit more background/rationale. Also, be sure and talk to your lead for further clarification, or you can reach out to me (Mark Wilkie) directly. - -## For a bit of history, a few things I think we’ve learned: -* We tried async triage, and it does actually work (sorta/mostly) and would work even better with the new CLI tool. -* Our teammates in Prague have appreciated the increased visibility (which is something we must continuing doing). -* Most every dev didn't like not meeting synchronously. -* The cost of not having serendipitous conversations is significant – but hard to quantify. -* Ironically, async triage seemed less inclusive because the participation has gone down, not up. (it’s been falling to a subset of folks to make sure things are triaged) - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CDevGuide%5Chybridtriage.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CDevGuide%5Chybridtriage.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CDevGuide%5Chybridtriage.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/IssueTrackingGuidance.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/IssueTrackingGuidance.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/IssueTrackingGuidance.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/IssueTrackingGuidance.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -# Adding a new issue -- If you know which epic the issue is a part of, add the issue to the appropriate epic using the *Epic Issue* field. This corresponds to the business priority the issue contributes to. **If you don't know which epic the issue belongs to**, do not fill this out—it will be filed appropriately at triage. -- Don't add it to a *Release* or *Project*. -- Feel free to assign it. This will be considered a recommend when it's triaged. -- Feel free to assign a milestone if you think you might know. This will be considered a recommendation when it's triaged. -- If the issue comes from a customer bug report, file it under the **First Responder** label. If the issue is specifically blocking a customer, assign it the **Critical** label as well. - -# Security issues -- If the issue has potential security impact, it should *not* be filed on GitHub. Instead, file it under [AzDO (dnceng/internal)](https://dev.azure.com/dnceng/internal/_workitems/) and make sure to triage it against SDL's [Security Bug Bar](https://aka.ms/sdlbugbar). -- Once filed, file a "tracking issue" on GitHub so that we can keep track if it through regular standup work. These issues should be titled something like "AzDO Issue #[Issue Number]" and contain a link to the AzDO issue. -- All discussion of the issue should take place on AzDO or internal email, *not* on GitHub. - -# Why we're doing this at all -- We'd like to lower the "pain" of entering and tracking our work. We've got consistent feedback that GitHub issues are much easier to use than AzDO - although not as feature-rich. However, we need everyone's participation to make this work... -- There's general consensus that there's better visibility and that things will be generally more accessible. - -# Process Notes -- All issues (regardless of type) will be triaged on a regular basis. -- Triage will "fill out" the issue as needed so that it's actionable. -- If triage assigns an issue to a sprint (milestone), this is a recommendation only. The actual assignments are done during sprint planning. -- During sprint planning, all issues assigned to the sprint being planned will be considered first, and then the backlog considered after that. This means that if you think something is important, please assign it to the sprint you'd like to see it get done in as a recommend. (then follow up in person too of course as appropriate) - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CDevGuide%5CIssueTrackingGuidance.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CDevGuide%5CIssueTrackingGuidance.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CDevGuide%5CIssueTrackingGuidance.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/meetings.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/meetings.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/meetings.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/meetings.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,48 +0,0 @@ -# Meetings - -## Introduction -In this new hybrid world there are many challenges ahead. This is our current attempt as a team to navigate. We know we're wrong at least in some ways, but will bravely move forward, learn, and then adjust as appropriate. - -## Definition -To distinguish team gatherings that fall into the guidelines below from others, we should define what we mean by a meeting. -- A **Meeting** - for the context of our principles and guidelines below - is any gathering of the team where a planned discussion or decision is made that will long lived (more than a week). -- Team social events (such as the weekly Prague Coffee Break or the Redmond Happy Hour), Stand Up (the daily one in Prague, the twice weekly one in Redmond, and the daily FR one), and 1:1 do not meet our definition of a meeting. - -## Principles -- All of our meetings must be inclusive of everyone who wants to participate. -- Meetings should be reserved for situations and discussions that are unreasonable or silly to have async. -- Meetings are an important, necessary, and key part of belonging. -- Social interactions are also a priority. (not just the agenda) -- Nobody should feel like they *only* go to meetings all day - -## Guidelines -- Every meeting is a Teams meeting. (regardless if synchronous or async) -- Meetings should start after the hour (or half hour) so that folks have breathing space in between. At least five minutes are recommended, and ten minutes if reasonable. -- Every meeting has recording turned on by default -- A meeting agenda articulating what is hoped to be accomplished is highly encouraged. -- Notes that articulate the main points discussed and conclusion are sent out after every meeting that is of general concern. (1:1's don't count of course) -- When folks are meeting physically together, laptops (or other devices) should be used to aid remote folks. (see logistics note below) -- The meeting organizer should *always* have a laptop. -- Attendees (remote and in-person) should use the "hand raise" feature to request time to speak. The meeting organizer, or her designee, should monitor and use this list. -- The meeting organizer, or her designee, should monitor Teams chat for relevant meeting information. -- It's probably better to schedule meetings in a conference room and/or focus room so as to not disrupt the team room. However, there are undoubtedly times when having the meeting in the team room is appropriate. -- Only one person should speak at a time - - Modern conference room microphones are sensitive. Even quiet side conversations come through clearly to remote attendees. - - Simultaneous speakers in a conference room quickly become incoherent to remote attendees and make it difficult to understand individuals. - - Use the Teams meeting chat for side conversations or request the floor from the meeting organizer. -- Close meetings on time. Conference rooms may be booked by others, which can cause an abrupt ending (and remote folks do not get to participate in the wrap-up hallway conversations that can happen after). - -## Logistics -- A pool of laptops is available which can be 'checked out' and used for meetings. At this time, there's probably not enough for everyone, but we do have a few already. Also, most devs now already have a laptop which is helpful. -- Meeting notes: - - V-Team meeting notes are posted in the v-team channel in Teams. - - Inter-Team notes are sent to the invite list. - - General team meeting notes are sent to the whole team alias. - -## Possible Feature Requests -- Teams to add the ability to auto deep link portions of the transcript - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cmeetings.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cmeetings.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cmeetings.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/readme.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/readme.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/readme.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/readme.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,50 +0,0 @@ -# Team Guide - -## Introduction -The intent of this document is to describe (not prescribe) how we do things within our team. Every team has their norms, and we're no exception. Given the shift to hybrid, coming up with our new norms is critical. This document hopes to reflect and articulate what we've learned that works. - -This is a living document, meant to be updated as we learn and adjust. That is to say, this document should say *something* about most everything we do - and then we change it as we see fit. - -## Team Guide Principles -- Every person on the team is encouraged to submit a PR to this doc as they see things that could/should be improved. -- It's more important to try *something* than to find the perfect solution. -- Experiment, learn, adjust and try again. - -## Dev Guide Discussion Guidelines -As we try things, find they do/don't work, there's going to be a lot of discussion - with a wide variety of views. This is good. As we discuss and wrestle with these challenges before us, let's keep the following in mind: -- Pointing out that something simply won't work is only helpful when there's also a proposal as we're looking for our best guess to try. -- Expressing concerns is different than stating that something just won't work. We should all express our concerns. -- Making an extra effort to point out parts that *should* work is very helpful. Not only does this improve how we all feel about things as a whole, but it serves as a reminder of what not to get rid of. -- Keep in mind that nobody actually knows how this will work. We're all figuring this out together. -- As always, be kind. -- It's very important to capture data such that we're able to better explain our learnings. This will help also with any infrastructure investments we might need to make. -- We will discuss the current state of the hybrid/async process during our monthly retrospectives to determine what is working, what isn't working, and how we can improve our process. - -## What Success Looks Like -Let's not forget the whole point of why we're trying to write down how we think we should best work as a team. -- Each team person feels like they belong, and are able to do their best work. -- Team impact and throughput remains high (or even increases). -- Where the dev lives, or what time of day they’re working, has limited effect on their own personal and overall team effectiveness. -- Working groups (v-teams) span the globe, yet are effective. -- Devs are often able to arrange their day and work when they are most productive and best for their situation. -- Devs are rarely blocked by others in making forward progress. -- Team diversity continues to increase. -- Team morale and satisfaction are high. -- Team responsiveness continues to be high and within the SLA. - -## Table of Contents -This is a work-in-progress and is far from complete. The idea is to start with the trickiest areas we think we'll encounter after Stage 6 re-opening. - -- [Hybrid Team Principles](hybridprinciples.md) -- [Making Hybrid Work....Work](hybridcollab.md) -- [General Availability Guidance](availability.md) -- [General Meetings](meetings.md) -- Criteria for when to have synchronous meetings -- [Hybrid Triage](hybridtriage.md) -- [Stand Ups](standup.md) -- [Documentation Guidelines](documentationguidelines.md) -- [Happy Hour](happyhour.md) - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CDevGuide%5Creadme.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CDevGuide%5Creadme.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CDevGuide%5Creadme.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/standup.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/standup.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/DevGuide/standup.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/DevGuide/standup.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -# Standup Guidelines - -* Standups should be both in-person and Teams meetings -* Standups occur twice a week on Tuesdays and Thursdays -* All team members are expected to attend standup, except when there are conflicts -* Standups can and will be preempted by meetings like all hands, Q&As, other org/division-wide meetings, and holidays. -* The in person standup will take place in the team room, and the person running the meeting should share their screen on the television in the team room. -* Where possible, an external mic should be used for the standup meeting so that those joining by Teams can hear everyone at the in person meeting. -* Standup will use the [Standup View](https://github.com/orgs/dotnet/projects/86/views/10) of the .NET Core Engineering Services board. - * The board should be organized such that it is grouped by the assignee, with issues in the Backlog (no status) or Done columns not shown. - * FR issues are also not shown on the Standup view - * All team members are responsible for updating their issues to make sure they are assigned to them and the status of the issues set -* All team members are encouraged to stand during standup, but sitting is ok as well. -* Standup should focus on quick status updates, with implementation details handled offline where possible. -* While off topic conversations are fun and a good part of our culture, they should be kept to the end of the meeting so that people who need to leave can leave, without feeling like they may miss out on important business - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cstandup.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cstandup.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CDevGuide%5Cstandup.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/artifact-maintenance-core-eng-14605.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/artifact-maintenance-core-eng-14605.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/artifact-maintenance-core-eng-14605.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/artifact-maintenance-core-eng-14605.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,77 +0,0 @@ -# dotnet-helix-machines Artifact Maintenance - -Epic: https://github.com/dotnet/core-eng/issues/14605 - -The core of this issue is that we don't have a process for updating our helix-machines artifacts on a regular cadence of any kind. -The purpose of this document is to lay out such a process for comment and ridicule. - -## Goal -We want to create a process that a team of vendors can execute on a regular cadence to keep the artifacts we install on our machines up to date. -No automation is planned as part of this process. - -There are two primary reasons for this work: -1. Security – keeping old artifacts can lead to potential vulnerabilities as minor version updates frequently contain security updates. -2. Stability – minor version updates often contain simple bugfixes that may require patching. - -## Stakeholders -Primarily the .NET Engineering Services team, but ultimately all customers using dotnet-helix-machines since not updating artifacts on a regular -cadence can lead to security issues, and updating them to versions that have breaking changes can obviously cause issues for them as well. - -## Problems -The following problems have been copied directly from the epic for reference. - -* For some artifacts, we don't really know what version we get because we're asking a given source (Windows, package manager, etc) for the name directly; this is thus not logged at all. -* Given we copy many installers and packages to our own storage accounts, and these packages may require manual intervention, this copying is sometimes difficult to automate. -* Even when we do know there is a newer version, we often cannot just take major release updates to a component as this would be breaking/require a hefty re-write of the code using this dependency. -* When we can tolerate them, we need a strategy to deal with major version updates, involving making sure a validation plan is in place, and ensuring a process to communicate to product teams to let them know - -### Necessary Ongoing Tasks -While automation can be improved to make this better, these sorts of things need to happen on a regular cadence: - -* Check for the latest version available (will vary by OS) and compare what version we have. -* Proactively try to adopt newer versions of dependencies before required -* Teach helix machines to have more info about artifacts (such as "where do we get this one?", "reasons to avoid major version upgrades", simple update instructions, etc.). This could be as simple as a -required text file in the artifact directory. - -## Proposed Implementation -I think that we should start treating artifacts in a way similar to how we now treat secrets. At a high level, this means: - -* We should include metadata on updating them – every artifact should have an `README.md` document included alongside it (similar to the validation -scripts we require today). Artifacts missing these will fail the build. This document will include detailed information on updating the artifact (e.g. where -the artifact comes from, how to tell if there is an updated version, the major version to pull from, where to upload the artifact, what part of the YAML to update accordingly, etc.). -* The operations vendor team should have a scheduled monthly pass of all artifacts and follow the instructions laid out in the README.md. -* We should add as many assets to Component Governance as possible so that we receive updates on CVEs so we can do an immediate update. This could possibly be done via -an automatically generated cgmanifest.json. - -### netcorenativeassets -Currently, most of our artifacts are stored in the netcorenativeassets storage account and downloaded from there. This is a good model from a security -perspective; however, we currently only update those artifacts on-demand. For these artifacts, the vendor will need to check the canonical -source for these artifacts (e.g. python.org for python) for the most recent version of the artifact, upload it to netcorenativeassets, and then create -a PR to dotnet-helix-machines with the update version. **This will only be done once newly released artifacts are mature unless there is a CVE in our -currently-used version**. "Mature" in this case means the artifact has been released for over a month. - -### Package managers - -The north star here is to eventually pull everything from private Azure DevOps feeds. - -#### Pip -Pip should use internal feeds which upstream to public for now. Eventually, we should migrate to Terrapin. Once we're using Terrapin, we should stop verison -pinning as using latest from our private feed will be more than adequate. - -#### Linux packages -Currently, the accepted model for Linux packages is to use the standard package manager's repositories. Because we do not ask for specific versions -of these packages, we make the (possibly dangerous) assumption that they are kept up-to-date. Presumably, these may eventually be moved to -internally-controlled package repositories. - -### Handling Major Version Updates -As mentioned in the problems section, major version updates are a sticky problem to solve as they potentially have breaking changes for our customers -and thus require manual review. At end-of-life for a major version, we should force an upgrade to a more recent version of the artifact and remove it -from our machines. Otherwise, on requests for major versions, we can deploy them side-by-side with previous versions to reduce pain. - -## The Work -In addition to further documenting the process for maintaining artifacts, the primary work for this epic will actually be writing update -docs for all of our currently-existing artifacts. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cartifact-maintenance-core-eng-14605.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cartifact-maintenance-core-eng-14605.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cartifact-maintenance-core-eng-14605.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/async-triage-core-eng13288.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/async-triage-core-eng13288.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/async-triage-core-eng13288.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/async-triage-core-eng13288.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,62 +0,0 @@ -# Asynchronous Triage Bot - -We want to implement a mechanism that will allow newly-created issues on GitHub that still need to be triaged to automatically have their own dedicated conversation started on Teams. It would also be useful to denote Teams conversations as [Triaged] or [Needs Triage] in Teams for clarity. - -See Epic for more context: https://github.com/dotnet/core-eng/issues/13288 - -## Stakeholders - - .NET Engineering Services team - -## Risk - -### Unknowns and Open Questions -- How can we distinguish if an issue needs triage, or if it was just created and will immediately have an Epic assigned to it? ([link to issue](https://github.com/dotnet/core-eng/issues/13457)) - - Suggested - a background job that occasionally scans for unassigned issues and adds a conversation on Teams if necessary, rather than generating a conversation every time an issue is created. This also addresses an interesting case in which an issue was assigned to one Epic, then unassigned. -- Some issues may not need to be assigned to an Epic (ex. customers that open issues for themselves). - - Suggested - marking these issues with a label for the bot to ignore, or triage assigns them to the "work tracking for other teams" epic. - -### Proof of Concepts -- Verify that we are able to open a new conversation in a channel on Teams when a new issue is opened on GitHub. Try to utilize some similar functionality to the FR Mention Bot. -- Verify that additional comments added to an issue update their dedicated Teams conversation. -- Verify that existing Teams conversations can be updated when triaged, such as denoting [Triaged] in the conversation title. -- Verify that we can grab all the existing unassigned issues from GitHub (this will require the use of the ZenHub API to check if it is in an Epic). - -### Dependencies -- GitHub API -- ZenHub API -- Teams - -Additionally, the solution should mostly be a new addition to the Arcade Services repo. Note that most of the GitHub webhook functionality is already implemented in Arcade Services. The goal to have the work completed by is September 10th at the latest. - -## Serviceability - -### Testing -To avoid sending actual requests to Teams and cluttering up the channel, we can test some functionality of the code by creating mock HTTP calls and checking if they send requests in the right cases. We can also try creating a hidden channel on Teams for integration testing. - -### Security -Identifying secrets that will be used include the Teams channel connector URI and authentication for the GitHub app. Note that all PII is owned by GitHub and Teams. -- The "bot" account that will be posting to Teams will also require authentication and must have permission to access channel messages, add replies to a discussion, etc. -- Setting up repro/test/dev environments: this will vary based on implementation details; the environment may simply be able to be set up and opened as a VS project, or might require a more complicated process. - -### Rollout and Deployment -- The solution will be most likely be deployed on Arcade Services, and thus on the Arcade Services cadence. Once deployed, check the Async Triage channel on Teams for updates. -- While nothing is being deprecated, we will probably remove the "Needs Triage" and "I Think This is Triaged" Fabric bot tags once the solution is deployed. -- What are the risks during deployment? - - If the solution stops working, new untriaged issues may go unnoticed without a conversation being created on Teams. It may require some monitoring to make sure that untriaged issues are consistently being added on Teams. - -## Usage Telemetry -Suggestions for tracking "usefulness" of the solution include measuring the average amount of time it takes for an issue to be triaged, how many people are involved, the average length of conversations, etc. However, more discussion is needed to figure out how the necessary information will be collected. - -## Monitoring -- Is there existing monitoring that will be used by this epic? - - On the Teams end, we can monitor if there are an abnormal amount of non-successful requests made to Teams. - - Suggested - using Azure Application Insights to monitor the functionality of the GitHub controller. -- If new monitoring is needed, it should be defined and alerting thresholds should be set up. - -## FR Hand off -- We should create some documentation on what functionalities the solution currently possesses and link to the code for more information. The code should also be clearly documented. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Casync-triage-core-eng13288.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Casync-triage-core-eng13288.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Casync-triage-core-eng13288.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/automated-image-generation13997.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/automated-image-generation13997.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/automated-image-generation13997.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/automated-image-generation13997.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,162 +0,0 @@ -# Automated Image Generation - -[Link to Automated Image Generation Epic](https://github.com/dotnet/core-eng/issues/13997) - -## Summary - -### Motivation -Currently only way how to generate Helix custom images for our Windows queues is to ask DDFUN. This happens on a weekly basis, always when we want to update Visual Studio versions or include the newest Windows patches. This process has several drawbacks from allocating someone from DDFUN team to introduction of typos by manual steps. - - -### Goal -The goal of this epic is to automate the process of custom image generation, so this can be done by our team or vendors, without the need of external teams. These are images which contains various versions of Visual Studio and various versions of Windows. We will introduce a new automated and monitored process which removes the need for manual steps done by DDFUN. - -When this is completed we will be able to: -* generate images ourselves without spending time on coordination with DDFUN, similarly to other teams using Image Factory -* specify new versions at one place instead of tens of configuration files -* automatically regenerate images on change of configuration files -* monitor completion of process instead of relying on notifications from DDFUN - -Part of this epic is to take the ownership of [custom image definitions](https://devdiv.visualstudio.com/XlabImageFactory/_git/ImageConfigurations?path=%2FMonthly%2FHelixBaseImages) that currently reside with DDFUN to get complete control over the definitions and to be able to run our automation against them seamlessly. This will be part of new documentation so maintenance of these definitions can be done also by vendors. - -### Implementation Details - -Let's start with a scenario where we need to update Visual Studio 2019 Preview version per our [schedule](https://dev.azure.com/dnceng/internal/_wiki/wikis/DNCEng%20Services%20Wiki/107/VS2019-Upgrade-Schedule) which has to be done almost every week. - -Currently this change requires update of same values in six [image definitions](https://devdiv.visualstudio.com/XlabImageFactory/_git/ImageConfigurations?path=/Monthly/HelixBaseImages/VS2019Preview). Specifically: -* update artifact windows-vs-willowreleased, set parameter VSBootstrapperURL to a new value. -* update version in parameter CustomImageName under Destination. - -To simplify this, we will introduce templating, so variables are defined at exactly one place and are not duplicated across multiple files, similarly to what we have in OSOB. - -#### Example: -Instead of hardcoding the same version and the same URL at [six places](https://devdiv.visualstudio.com/XlabImageFactory/_git/ImageConfigurations?path=/Monthly/HelixBaseImages/VS2019Preview), we introduce template variable {VS_2019_PREVIEW_URL} which declares an URL to Visual Studio artifact and the template variable {VS_2019_PREVIEW_VERSION} which declares version of Visual Studio. - -All templated variables will be stored in one file. - - -1. Given our example scenario, we will update variables VS_2019_PREVIEW_URL and VS_2019_PREVIEW_VERSION. And this change will be pushed into the repository. This change triggers our new build pipeline under Azure DevOps. - -2. The pipeline executes command line tool which processes all payloads and substitutes all template variables from the variable file and call Image Factory with this payload. To prevent duplication we will calculate hash of payload and store it a simple Azure storage table and call Image Factory only for payloads which haven't been processed yet. If needed it will be possible to force rebuilding of images. - -3. The pipeline won't block until all images are completed. It will post Image Factory jobs, store tracking ids in Azure storage table mentioned above and finish. - -4. To get results, the same pipeline will be executed periodically (e.g. every hour) and check states of all pending Image Factory jobs and provide summary report so it's clear if all images that were requested are ready. In case any image build fails, the pipeline will fail and FR will be notified by email. - -5. Once Helix custom images are generated, FR has to create an OSOB PR with updated image names. Existing OSOB post validation performs version test of Visual Studio. - -Documentation of the Image Factory API can be found [here](https://devdiv.visualstudio.com/XlabImageFactory/_wiki/wikis/XlabImageFactory.wiki/6330/AccessingImageFactory). - - -## Take ownership of Helix custom image definitions - -Making custom image definitions templated requires modifications. This is why we need to move all [definitions](https://devdiv.visualstudio.com/XlabImageFactory/_git/ImageConfigurations?path=%2FMonthly%2FHelixBaseImages) under our repository. It was confirmed by DDFUN (Casey) that these definitions aren't shared with any other team. Beside changing URL and version the structure is left unchanged, so there isn't any additional maintenance cost related to owning these definitions. - -Currently the definitions with DDFUN use YAML only to be converted to JSON payloads. As part of the move I would suggest to start using JSON file format as it's expected input of the Image Factory. The only benefit of YAML are comments, but in our case these comments are copy pasted across all definitions and don't add any additional value. - -Here is an example of a fragment of a templated image definition with template variables VS_2019_PREVIEW_URL and VS_2019_PREVIEW_VERSION: -``` -{ - "Artifacts": [ - { - "Name": "windows-vs-willowreleased", - "Parameters": { - "WorkLoads": "reduced", - "Sku": "Enterprise", - "VSBootstrapperURL": "{VS_2019_PREVIEW_URL}/vs_Enterprise.exe" - } - } - ], - "Destination": [ - { - "StorageAccountName": "heliximgfctdncwus2", - "CustomImageName": "Helix-Server-DataCenter-19H1-ES-ES-VS2019-Preview-Enterprise_{VS_2019_PREVIEW_VERSION}", - "SubId": "84a65c9a-787d-45da-b10a-3a1cefce8060" - } - ] -} -``` - -_Note: Nested templated variables won't be supported._ - -Variables file: -``` -{ - "VS_2019_PREVIEW_URL":"https://aka.ms/vs/16/pre/133508311_-1151188015", - "VS_2019_PREVIEW_VERSION":"16_11_2_1", -} -``` - - -## Stakeholders - -- .NET Engineering Services - -## Risk - -- Are there any POCs (proof of concepts) required to be built for this work? - - POC for Visual Studio images was done before creating this one pager. - -- What dependencies will this epic have? Are the dependencies currently in a state that the functionality in the epic can consume them now, or will they need to be updated? - - This epic depends on external service Image Factory. It's functionality is currently stable and no breaking changes are planned. I asked to notify our team in a case of changes. - -- Will the new implementation of any existing functionality cause breaking changes for existing consumers? - - Implementation of this shouldn't cause breaking changes as all issues with images should be detected during OSOB validation phase. - - -## Serviceability - -- How will the components that make up this epic be tested? - - CLI will be tested by unit tests. There will be also scenario test which generates one image and verifies it. - -- Identifying secrets (e.g. PATs, certificates, et cetera) that will be used (new ones to be created; existing ones to be used). - - Existing secrets: - * image-factory-tenant-id - * image-factory-client-id - * image-factory-client-secret - * image-factory-resource-id - - New secret: - * image-factory-state-connection-string - -- Does this change any existing SDL threat or data privacy models? (models can be found -in [core-eng/SDL](https://github.com/dotnet/core-eng/SDL) folder) - - It doesn't change SDL threat or data privacy models. - - -## Rollout and Deployment - -- A new Azure DevOps build pipeline will take specific version of this tool from artifacts and executes it. - -- In a case of issues, it is still possible generate images manually. - - -## Usage Telemetry -- This tool is internal only. Basic information about runs will be available from the pipeline history. We don't plan to include any additional data, unless they are requested. If we start experience problems with the image generation though, we might need to start gathering some reliability data. - -## Monitoring -- Monitoring is based on result of the build pipeline. It will send email notification on any failure. If there is any issue, it should be picked up by FR. - -## FR Hand off -- We will create documentation about - - How to generate custom images with Visual Studio - - How to use the tool in manual mode - - What to do when pipeline fails the build - -- The policies of generating images overlaps with Matrix of Truth epic and will be further discussed with the epic owner. - -## Future outlook -- Execution of the process should be possible to be done by vendors with minimal cost. - - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cautomated-image-generation13997.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cautomated-image-generation13997.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cautomated-image-generation13997.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/bcm-emergency-staging-pipeline-optimizations-12261.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/bcm-emergency-staging-pipeline-optimizations-12261.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/bcm-emergency-staging-pipeline-optimizations-12261.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/bcm-emergency-staging-pipeline-optimizations-12261.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,124 +0,0 @@ -# Emergency staging pipeline optimizations - -## Goals - -As part of https://github.com/dotnet/arcade/issues/12261 we need to introduce emergency mode to Stage-DotNet and Stage-DotNet-Test pipelines that can be run to prepare a .NET release as fast as possible. Currently the critical stages in the pipeline depend on multiple validation stages (Source Code Validation, Validation, Required Validation) that run for a long time, which is one of the causes for the overall long run time of the pipeline. We need to implement a sped up version of the pipeline, skipping some of the validation jobs and running stages in parallel where possible. - -## Proposed Implementation - -The proposed implementation is introducing a "emergency switch" parameter in the Stage-DotNet pipeline. Stage dependencies can be rearranged according to this parameter. That will require no changes to the release pipeline that depends on the staging pipeline outputs. (Stage-DotNet is coded as a resourse in Release-DotNet-*). We can test the changes using the available testing pipeline (Stage-DotNet-Test) as it uses the same yaml. - -Another approach was discussed - creating a separate pipeline that will contain the stages in the emergency order. This approach would require changes to the release pipeline and would be harder to maintain as changes to Stage-DotNet would also need to be mirrored to the new pipeline and that can cause divergence from the original in the future. Therefore, the "emergency switch" parameter is the better option. - -The sequence of stages in the current pipeline is: - -```mermaid -flowchart LR - prep["Prep Ring \n ~30min"] --> prep_override[Prep Ring Override] - prep_override --> signing["Signing Ring \n ~50min"] - prep_override --> source_code_validation["Source Code Validation \n ~40min"] - source_code_validation --> source_code_validation_override[Source Code Validation Override] - signing --> required_validation["Required Validation \n ~1h"] - signing --> validation["Validation \n ~5h"] - required_validation --> sbom_generation["SBOM Generation \n ~20m"] - required_validation --> required_validation_override[Required Validation Override] - validation --> validation_override[Validation Override] - required_validation_override --> publishing_v3_signed["Publish Post-Signing Assets \n ~1h20m"] - required_validation_override --> post_signing_publishing["Publish Signed Assets \n ~1h30m"] - required_validation_override --> vs_insertion["VS Insertion Ring \n ~50m"] - sbom_generation --> sbom_generation_override[SBOM Generation Override] - vs_insertion --> vs_insertion_override[VS Insertion Override] - vs_insertion_override --> cti_sign_off[Wait for Test Team Sign Off] - cti_sign_off --> staging["Staging Ring \n ~1h10m"] - source_code_validation_override --> staging - staging --> finalize_sign_off[Sign off for finalizing the release] - finalize_sign_off --> finalize_staging[Finalize Staging Ring] - finalize_sign_off --> publishing_v3_validated["Publish CTI Validated Assets \n ~1h20m"] - finalize_staging --> handoff_sign_off_dotnetcsainternal[Approve Publishing to Dotnetcsainternal] - handoff_sign_off_dotnetcsainternal --> handoff_publishing_dotnetcsainternal["Handoff Publishing Ring (dotnetcsainternal)"] - classDef default fill:#50C878, stroke:#023020; - classDef Override fill:#ECFFDC, stroke:#023020; - class prep_override,source_code_validation_override,required_validation_override,sbom_generation_override,vs_insertion_override,validation_override Override; -``` -*Light colored stages are manual validation that overrides another stage (e.g. Prep - Prep Override). They are run if the corresponding stage fails or succeeds with issues and skipped otherwise. - -If the parameter set to true, change the sequence of stages/jobs in the following way: - -```mermaid -flowchart LR - prep["Prep Ring \n ~30min"] --> signing["Signing Ring \n ~50min"] - prep ---> source_code_validation["Source Code Validation \n ~40min"] - signing --> vs_insertion["VS Insertion Ring \n ~50m"] - signing --> required_validation["Required Validation \n ~1h"] - signing --> validation["Validation \n ~5h"] - signing --> sbom_generation["SBOM Generation \n ~20m"] - signing --> publishing_v3_signed["Publish Post-Signing Assets \n ~1h20m"] - signing --> post_signing_publishing["Publish Signed Assets \n ~1h30m"] - signing --> cti_sign_off[Wait for Test Team Sign Off] - cti_sign_off --> staging["Staging Ring \n ~1h10m"] - staging --> finalize_sign_off[Sign off for finalizing the release] - finalize_sign_off --> finalize_staging[Finalize Staging Ring] - finalize_sign_off --> publishing_v3_validated["Publish CTI Validated Assets \n ~1h20m"] - classDef default fill:#50C878, stroke:#023020; -``` - -- The Staging Ring - - skip the `validate-staging-inputs` and `validate_staging_outputs` step - - dynamically change dependencies to - - prep - - prep_override - - signing - - cti_sign_off - - dynamically change conditions -- VS Insertion Ring - - skip `validate_vs_insertion_inputs` job - - dynamically change dependencies to - - prep - - prep_override -- Publish Signed Assets - - depends on: - - signing - - add a condition that signing was successful -- Publish Post-Signing Assets - - depends on: - - prep - - prep_override - - signing - - remove required_validation conditions -- Validation Ring - no changes -- Sbom Generation Ring - - depends on: - - prep - - prep_override - - signing - - no changes in conditions -- Required Validation Ring - no changes -- Source Code Validation - no changes -- Signing Ring - - skip `validate_signing_inputs` step -- Prep Ring - no changes -- Configure stages to have no override on failure - -## Risks - -- the release pipeline is closely connected to the staging - Stage-DotNet is listed as a resource in Release-DotNet from which build artifacts are being downloaded, the release pipeline also expects that assets are published to certain feeds and storage acccounts. We need to check that all assets it needs are being published with the expected changes and in the expected location. - -- make sure that the critical stages of Stage-DotNet have all the needed assets. It is possible that additional changes need to be made in the jobs consuming or publishing artifacts, but on a first glance validation stages don't produce outputs that are used in later stages. - -## Testing - -The change can be tested by running the Stage-DotNet-Test pipeline. It uses the same yml as Stage-DotNet but uploads assets to temporarily created feeds and blob storage containers so that we can run the pipeline multiple times without uploading to the official places. We should run it with the emergency switch on and off and make sure that -- stages are run in the correct order in both emergency and regular scenario -- adding the new parameter doesn't introduce any errors in the current staging process -- all stages from the diagram are run so that: - - all files produced by the staging pipeline are published (to feeds and blob storage containers) in both the emergency and regular scenario - - the build artifacts that are used by the release pipeline are published (`config.json`, `manifest.json`, `signed/*`, `signalr-signed/*`, `release-manifests/*`) - -## Open Questions - -1. How does the change need to be communicated? - -2. Should we create a separate 1ES pool for the emergency pipeline to decrease wait time, also could we unite jobs with the same goal? (suggested by Djuradj) - - - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/core-eng-repository-migration-15084.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/core-eng-repository-migration-15084.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/core-eng-repository-migration-15084.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/core-eng-repository-migration-15084.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,90 +0,0 @@ -# Core-Eng Repository Migration - -It is discouraged to have repositories without code. Our documentation and issues are fragmented between multiple places, and it's hard to orient in it. To make things cleaner, it makes sense to migrate all valid items from core-eng repository and archive this repository. - - -## Stakeholders - -* .NET Core Engineering Services team (contact: @dnceng) -* .NET Core Engineering Partners (contact:@dncpartners) - - -## Components to change - -This work consists of four parts: -* Move repository files (documentation only) -* Move Wiki -* Code changes -* Issues / Epics -* Guidance for security issues - -### Move repository files (documentation only) -This change includes: - * [DevDocumentation](https://github.com/dotnet/core-eng/tree/main/DevDocumentation/DevWorkflow/Design) - contains new documentation for DevWorkflow. The whole folder should be moved. - * [Docs](https://github.com/dotnet/core-eng/tree/main/docs) - some items seems to be obsolete, but there are recent (<=1 year) updates. - * [Documentation](https://github.com/dotnet/core-eng/tree/main/Documentation) - the whole folder should be moved. - -For each document we need to find the best location. Internal documentation shouldn't go to the Arcade repository which is available for public. List of target locations: - * [arcade documentation](https://github.com/dotnet/arcade/tree/main/Documentation). - * [arcade-services documentation](https://github.com/dotnet/arcade-services/tree/main/docs). - * [helix-service documentation](https://dev.azure.com/dnceng/internal/_git/dotnet-helix-service?path=/docs) - * [helix-machines documentation](https://dev.azure.com/dnceng/internal/_git/dotnet-helix-machines). - * [AzDO wiki](https://dev.azure.com/dnceng/internal/_wiki/wikis/internal.wiki/1/Home) - -Note: While moving to a new location, we need to update links in the existing code. The rest of the [repository core-eng](https://github.com/dotnet/core-eng) seems to be safe to be deleted. - -### Move Wiki - -The whole wiki should be migrated. There are two options for new placement: -* [AzDO dnceng wiki](https://dev.azure.com/dnceng/internal/_wiki/wikis/internal.wiki/1/Home) - visible internally only -* [Arcade wiki](https://github.com/dotnet/arcade/wiki) - available to everyone - -If we split content between public and internal location, fragmentation of our documentation will increase. This is the reason why we are planning to move the whole wiki into AzDO. - -### Code changes - -This is to change configuration to point against a different repository and to update ZenHub logic to a new representation of epic. - -Affected components: -* Alerting under dotnet-arcade-services\src\DotNet.Status.Web -* RolloutScorer under dotnet-arcade-services\src\RolloutScorer -* Maestro under dotnet-arcade-services\src\Maestro -* Async triage tool under dotnet-helix-service\src\async-triage-cli - -### Issues / Epics - -There are many issues created long time ago which probably aren't valid anymore. We should be automatically migrating issues that are newer than a year. Epics could be older and should be automatically migrated if they contain at least one issue that is newer than a year. - -We should flag issues with the Security or EOC labels as not to be automatically migrated. We don't want to announce security issues to the public before they are fixed. - -Note: No issues will be closed automatically. The core-eng repository will be archived so if required, any issue can be moved manually in the future. - -### Guidance for security issues - -All issues with the Security or EOC labels should not to be automatically migrated. Security issues must not be announced to the public before they are fixed. We need to come up with new guidance for security issues. One option would be to track them as AzDO work items. - - -## Migration approach - -No item will be deleted as part of this change. Once all valid items are copied into a new location, the repository core-eng will be archived. - -## Rollout and Deployment - -* Affected components which points against core-eng repo has to be updated and deployed. See the list above. - -## Communication of changes - -FR has to be notified about: -* new location of documentation -* new location of alerts - -Partners have to be notified about: -* new location of documentation - -## Monitoring - -All affected components have to be verified after deployment. No additional monitoring is required. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Ccore-eng-repository-migration-15084.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Ccore-eng-repository-migration-15084.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Ccore-eng-repository-migration-15084.md) - Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/EpicDropDown.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/EpicDropDown.png differ diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/improved-container-image-lifecycle-arcade-10349.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/improved-container-image-lifecycle-arcade-10349.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/improved-container-image-lifecycle-arcade-10349.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/improved-container-image-lifecycle-arcade-10349.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,52 +0,0 @@ -# Improved Docker Container Image Lifecycle - -As part of [#10123](https://github.com/dotnet/arcade/issues/10123) to improve our docker container security and sustainability, we need to improve the container image lifecycle. Currently, our container definitions are stable, but rarely updated, with some of the definitions dating back several years. We don't have means to ensure that all container images we use contain the latest OS patches and CVE fixes. One of the main points of this proposal is to ensure that the containers are updated regularly, accepting servicing updates from the OS on a regular basis. The major business goals of this work are to make sure that: - -- Our container images are re-built regularly and they contain the latest underlying OS patches and CVE fixes -- There is a mechanism for updating the docker containers used by product teams so that they are always on the latest version of each container image -- There is a process and tools implemented for identifying and removing dockerfiles based on out-of-support base images -- There is a process and tools to delete out-of-date container images (older than 3-6 months) from MCR -- All images used in the building and testing of .NET use Microsoft-approved base images, either Mariner where appropriate, or [Microsoft Artifact Registry-approved images](https://eng.ms/docs/more/containers-secure-supply-chain/approved-images) where Mariner is insufficient - -## Stakeholders - -- .NET Core Engineering -- .NET Acquisition and Deployment -- .NET Product teams - -## Risks - -- Will the new implementation of any existing functionality cause breaking changes for existing consumers? - -The major risk in this portion of the epic is finding and updating all container usages by product teams, and making sure that moving them to the latest versions of the container images doesn't break their builds/tests because of missing artifacts. Our goal is to use docker tags to label the latest known good of each container image, and replace usages of specific docker image tags with a `---latest` tag. That way, much like with helix images, their builds and tests will be updated automatically when we deploy a new latest version. In the transition to latest images, we may find that older versions of a container may have different versions of artifacts installed on those containers, which could affect builds and tests. We will need to be prepared to help product teams identify these issues and work through them. - -- What are your assumptions? - - The Matrix of Truth work will enable us to identify all pipelines and branches that are using docker containers and which images they are using - - We will be able to extend the existing publishing infrastructure to also identify images that are due for removal - - All of our existing base images can be replaced with MAR-approved images (we can already see where this is not true, OpenSuse and Raspian are not available as MAR-tagged images, and Alpine will be deprecated soon) - - Most of the official build that is currently built in docker containers can be built on Mariner - - MAR-approved images are updated with OS patches and CVE fixes - -- What are your unknowns? - - What should we do about images that are not available as MAR-approved base images? - - How will we identify the last known good (LKG) for each docker image? - - What testing is currently in place for docker images, so that we can have confidence that updating the `latest` image will not break product teams? - - What is the rollback story for the `latest` tagging scheme? - -- What dependencies will this epic/feature(s) have? - -This feature will depend heavily on MAR-approved images (and whatever updating scheme they have for updating base images), as well as the existing functionality for building and publishing our docker container images. We will want to expand the existing functionality to allow us to 1) identify the last known good of each docker image and 2) tag that LKG with a descriptive `latest` tag. - -## Serviceability of Feature - -### Rollout and Deployment - -As part of this work, we will need to implement a rollout story for the new tagging feature. We do not want every published image to immediately be identified as the production version. To add some testing time, we will create a production branch of `dotnet-buildtools-prereqs-docker`, and perform weekly rollouts alongside our helix-service, helix-machines, and arcade-services rollouts. When we roll all of the known good changes in the main branch to production, those new images will then be tagged as the production versions. Daily images built in main will be considered staging images and will be avaible to customers if they so choose. We will also need a rollback story so that if an image breaks a product team's build or test, we can revert to a previously working version of the image. - -### FR and Operations Handoff - -We will create documentation for managing the tags so that when a rollback needs to occur, FR will be able to make those changes. Additionally, we will create documentation and processes that can be used by Operations and/or the vendors to handle any manual OS/base image updating or removing of old and out-of-date images from MCR, as necessary. We will also create documentation for responding to customer requests for new docker images, including where to get the base images, how to install required dependencies (though that is coming in a different one pager), and what the process is for adding new images when a major version update is requested for dependencies. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cimproved-container-image-lifecycle-arcade-10349.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cimproved-container-image-lifecycle-arcade-10349.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cimproved-container-image-lifecycle-arcade-10349.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/back-tested-accuracy-vs-time.svg dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/back-tested-accuracy-vs-time.svg --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/back-tested-accuracy-vs-time.svg 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/back-tested-accuracy-vs-time.svg 1970-01-01 00:00:00.000000000 +0000 @@ -1,1086 +0,0 @@ - - - - - - - - 2022-07-08T14:43:16.911650 - image/svg+xml - - - Matplotlib v3.5.1, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/design-mockup-justin-impl.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/design-mockup-justin-impl.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/design-mockup-justin-impl.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/design-mockup-justin-impl.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,76 +0,0 @@ -# Pipeline Status - -*The list of queues is cached weekly. If your PR changes what queues your pipelines use, this information will not show the updated queues.* - -Here's a list of the top 5 highest work item wait times: - -| Queue | Work Item Wait Time | Difference in Moving Avg | -| ---------------------------------- | ------------------- | ------------------------ | -| [`Windows.11.Amd64.Client.Open`]() | **43min 2s** | *+12%* 📈 | -| [`Ubuntu.1804.Amd64.Open`]() | **43min 2s** | *-3.0%* 📉 | -| [`Debian.11.Amd64.Open`]() | **43min 2s** | *+0.1%* 📈 | -| [`Windows.11.Amd64.Client.Open`]() | **43min 2s** | *+1%* 📈 | -| [`Windows.11.Amd64.Client.Open`]() | **43min 2s** | *-7%* 📉 | - - -## Grafana Dashboard - -For more in-depth information on the status of Helix, visit our [Grafana Dashboard](). - -## Your Queues - -☁️ **dotnet/runtime** is currently configured to submit to the following Helix queues: - -* `Alpine.313.Amd64.Open` -* `Alpine.313.Arm64.Open` -* `Alpine.314.Amd64.Open` -* `Alpine.314.Arm64.Open` -* `Centos.7.Amd64.Open` -* `Centos.8.Amd64.Open` -* `Debian.10.Amd64.Open` -* `Debian.10.Arm32.Open` -* `Debian.11.Amd64.Open` -* `Debian.11.Arm32.Open` -* `Fedora.34.Amd64.Open` -* `Mariner.1.0.Amd64.Open` -* `OSX.1015.Amd64.AppleTV.Open` -* `OSX.1015.Amd64.Iphone.Open` -* `OSX.1015.Amd64.Open` -* `OSX.1100.Arm64.Open` -* `OSX.1200.ARM64.Open` -* `OSX.1200.Amd64.Open` -* `Raspbian.10.Armv6.Open` -* `RedHat.7.Amd64.Open` -* `SLES.15.Amd64.Open` -* `Ubuntu.1804.Amd64` -* `Ubuntu.1804.Amd64.Android.29.Open` -* `Ubuntu.1804.Amd64.Open` -* `Ubuntu.1804.ArmArch.Open` -* `Ubuntu.2004.S390X.Experimental.Open` -* `Ubuntu.2110.Amd64.Open` -* `Ubuntu.2110.Arm64.Open` -* `Windows.10.Amd64.Android.Open` -* `Windows.10.Amd64.Client21H1.Open` -* `Windows.10.Amd64.Server2022.ES.Open` -* `Windows.10.Amd64.ServerRS5.Open` -* `Windows.10.Arm64.Open` -* `Windows.10.Arm64v8.Open` -* `Windows.11.Amd64.Client.Open` -* `Windows.7.Amd64.Open` -* `Windows.81.Amd64.Open` -* `Windows.Amd64.Server2022.Open` -* `Windows.Nano.1809.Amd64.Open` -* `openSUSE.15.2.Amd64.Open` - -🏢 **dotnet/runtime** uses the following on-prem queues: - -* `Some.OnPrem.Queue` -* `Some.OnPrem.Queue2` -* `Some.OnPrem.Queue3` - -*Was this helpful?* 👍👎 - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5CIncreaseVisibilityHelixQueues%5Cdesign-mockup-justin-impl.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5CIncreaseVisibilityHelixQueues%5Cdesign-mockup-justin-impl.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5CIncreaseVisibilityHelixQueues%5Cdesign-mockup-justin-impl.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/design-mockup.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/design-mockup.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/design-mockup.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/design-mockup.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,113 +0,0 @@ -# Pipeline Status - -*The list of queues is cached weekly. If your PR changes what queues your pipelines use, this information will not show the updated queues.* - -## Average Times - -| Item | Average Time | -| ----------------------- | ------------ | -| Obtain build machine | **13m 4s** | -| Helix tests to complete | **30m 2s** | -| Pipeline to complete | **1hr 18m** | - -*These estimated times are computed from historical data and may not accurately reflect the current status of AzDo and Helix* - ------ - -Here's a list of the top 5 highest work item wait times: - -| Queue | Work Item Wait Time | Difference in Moving Avg | -| ---------------------------------- | ------------------- | ------------------------ | -| [`Windows.11.Amd64.Client.Open`]() | **43min 2s** | *+12%* 📈 | -| [`Ubuntu.1804.Amd64.Open`]() | **43min 2s** | *-3.0%* 📉 | -| [`Debian.11.Amd64.Open`]() | **43min 2s** | *+0.1%* 📈 | -| [`Windows.11.Amd64.Client.Open`]() | **43min 2s** | *+1%* 📈 | -| [`Windows.11.Amd64.Client.Open`]() | **43min 2s** | *-7%* 📉 | - - -## Queue Insights - -❌ The queue [`OSX.1015.Amd64.Open`]() has a high wait time of Xhrs and Ymin. -* **Your tests will likely timeout**. -* Current queue count: **560** (*+57%* over moving average) -* Current work item wait time: **5hr 28min** - -⚠️ Currently, [`Windows.10.Amd64.Client21H1.Open`]() is experiencing a higher than normal work item wait time. -* Current queue count: **560** (*+57%* over moving average) -* Current work item wait time: **2hr 5min**. (*+22%*) -* There are no known issues with our infrastructure. -* ❗**There is currently a known issue with our infrastructure.** [Details.]() - -✅ [`OSX.1200.ARM64.Open`]() has unusually low traffic. -* Estimated time in queue: **3m 4s**. (*-34%*) - -## .NET Engineering Services Infrastructure Status - -| Product | Status | -| -------------- | :----: | -| Helix | ✅ | -| Queues | ⚠️ | -| On-Prem Queues | ❌ | - -See our [Helix status overview dashboard](). - -## Grafana Dashboard - -For more in-depth information on the status of Helix, visit our [Grafana Dashboard](). - -## Your Queues - -☁️ **dotnet/runtime** is currently configured to submit to the following Helix queues: - -* `Alpine.313.Amd64.Open` -* `Alpine.313.Arm64.Open` -* `Alpine.314.Amd64.Open` -* `Alpine.314.Arm64.Open` -* `Centos.7.Amd64.Open` -* `Centos.8.Amd64.Open` -* `Debian.10.Amd64.Open` -* `Debian.10.Arm32.Open` -* `Debian.11.Amd64.Open` -* `Debian.11.Arm32.Open` -* `Fedora.34.Amd64.Open` -* `Mariner.1.0.Amd64.Open` -* `OSX.1015.Amd64.AppleTV.Open` -* `OSX.1015.Amd64.Iphone.Open` -* `OSX.1015.Amd64.Open` -* `OSX.1100.Arm64.Open` -* `OSX.1200.ARM64.Open` -* `OSX.1200.Amd64.Open` -* `Raspbian.10.Armv6.Open` -* `RedHat.7.Amd64.Open` -* `SLES.15.Amd64.Open` -* `Ubuntu.1804.Amd64` -* `Ubuntu.1804.Amd64.Android.29.Open` -* `Ubuntu.1804.Amd64.Open` -* `Ubuntu.1804.ArmArch.Open` -* `Ubuntu.2004.S390X.Experimental.Open` -* `Ubuntu.2110.Amd64.Open` -* `Ubuntu.2110.Arm64.Open` -* `Windows.10.Amd64.Android.Open` -* `Windows.10.Amd64.Client21H1.Open` -* `Windows.10.Amd64.Server2022.ES.Open` -* `Windows.10.Amd64.ServerRS5.Open` -* `Windows.10.Arm64.Open` -* `Windows.10.Arm64v8.Open` -* `Windows.11.Amd64.Client.Open` -* `Windows.7.Amd64.Open` -* `Windows.81.Amd64.Open` -* `Windows.Amd64.Server2022.Open` -* `Windows.Nano.1809.Amd64.Open` -* `openSUSE.15.2.Amd64.Open` - -🏢 **dotnet/runtime** uses the following on-prem queues: - -* `Some.OnPrem.Queue` -* `Some.OnPrem.Queue2` -* `Some.OnPrem.Queue3` - -*Was this helpful?* 👍👎 - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5CIncreaseVisibilityHelixQueues%5Cdesign-mockup.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5CIncreaseVisibilityHelixQueues%5Cdesign-mockup.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5CIncreaseVisibilityHelixQueues%5Cdesign-mockup.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/roslyn-CI.svg dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/roslyn-CI.svg --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/roslyn-CI.svg 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/IncreaseVisibilityHelixQueues/roslyn-CI.svg 1970-01-01 00:00:00.000000000 +0000 @@ -1,1603 +0,0 @@ - - - - - - - - 2022-07-08T11:34:54.510301 - image/svg+xml - - - Matplotlib v3.5.1, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/increase-visibility-helix-queues-arcade8824.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/increase-visibility-helix-queues-arcade8824.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/increase-visibility-helix-queues-arcade8824.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/increase-visibility-helix-queues-arcade8824.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,153 +0,0 @@ -# Increasing visibility into the time builds & Helix tests take, and Helix errors - -## Goal and Motivation - -Our customers are dissatisfied with information about the status of the Helix queues in their given pipeline and the amount of time it takes waiting in queue to obtain a build machine. - -We currently have an awesome Grafana dashboard, but the discoverability is close to zero, and it doesn't clearly identify what is going on with the *overall* status of Helix. It does a great job of putting data into context (is this queue depth normal for this queue). - -The data it provides is too much for a typical dev. A dev's main concerns are: - -* How long will my build and tests take? -* This queue is taking really long, is everything okay? -* What's the overall status of Helix (outage, infrastructure issues, etc.) - -The dashboard doesn't answer these questions directly, but they can be *inferred* from the dashboard. - -We want a solution that puts this information right in front of devs. - -We can leverage our existing `BuildFailureAnalysis` projects to add another GitHub check that can present clear insights into the status of Helix, directly into the GitHub PR they are working on. - - -### Stakeholders - -* .NET Core Engineering Services (contact: @dnceng) -* Helix Customers (contact: @dncpartners) - -### Implementation - -**See [Design Mockup](IncreaseVisibilityHelixQueues/design-mockup.md) for a mockup of the overall, final design.** - -Our main goal is to add a new check, titled `Helix Queue Insights` that will be included directly in the PR a dev is working on. - -#### Subprojects - -This a list of all the features this one-pager is for. - -* Create a new check in the Github PR -* Show the dev a list of queues (separated by on and off prem) -* Show the top 5 queues with the highest work item wait time. -* Create "insights" into the queues of their pipelines. Tell the dev that queues are: - 1. **Extremely** high work item wait time. Is it likely this queue will timeout? - 2. **Heavier** than usual wait time. This queue wait isn't normal and it will take more time for this queue. - 3. **Lighter** than usual wait time. This test will run faster than normal! -* Estimated times for: - * Helix tests to complete - * How long it will take for a build machine to be obtained - * How long it will take for the entire pipeline to complete -* Tracking how accurate our estimates are (from the Estimated Times feature) -* A high level overview status of Helix - * Are Helix, our on-prem and off-prem queues, etc. operating normally? - * Red/Yellow/Green, similar to the [Azure DevOps Status](https://status.dev.azure.com/) -* Draw a detailed graph of how builds and tests flow to show the user. - * For example, the roots are the different builds and the leafs are different Helix queues - * This will show the dev the overall hierarchy of their pipeline and allow them to optimize their pipeline by submitting jobs early to busy pools or reducing queues they use (thanks Stu) - -#### Projects in Scope - -In the coming 10 weeks of the internship, I will limit my scope to the following projects: - -* Create a new check in the Github PR -* Show the dev a list of queues (separated by on and off prem) -* Show the top 5 queues with the highest work item wait time. - -The result I'm looking to achieve is [this mockup](IncreaseVisibilityHelixQueues/design-mockup-justin-impl.md). - -**Stretch goals:** -* Estimated times for helix tests and getting an AzDO build machine - * Machine Learning - - -### Technical Implementation Details - -1. Be notified of when a new PR is created. - 1. Our existing code can already do this. Specifically, our `AnalysisProcessor` in our `BuildResultAnalysisProcess` microservice. - 2. This needs to be changed to add a new Checkrun, as the Helix Queue Insights will be its own checkrun to avoid running into the 65k character limit. In addition, the Build Analysis page gets overwritten when any of the pipelines in the repo completes. We also don't want to mix build results with the status of Helix queues. - -2. Determine which queues the repo uses. - 1. We will use the Matrix of Truth for this data. - 2. Their data has is built from a job that Ilya mentioned at least updates once a week, and we can pull this information programmatically. - -3. Query the work item wait time and queue size for that pipeline's list of queues. - 1. Currently Grafana has this data, with Kusto queries that we can pull and use. - 2. We will simply pull the queries that Grafana uses them to present the data. - 1. This creates a small issue of having the same query in two locations, the Grafana and this new feature. Stu and I discussed, and he mentioned that the queries haven't changed much (if at all) since the dashboard was created, so it should be okay. - 2. The other option is to query Grafana for the data (which we can do), but then this creates an unnecessary tight dependency on Grafana, - -4. Process the data to compute moving average, and detect abnormalities. - 1. This is interleaved with the previous step, but we'll need to compute the moving average using a Kusto query. - 2. Compute the percent differences between the current work item wait time and the moving average. - -5. Calculate the time for the entire pipeline to complete - 1. Build Analysis currently can determine what pipelines a PR will trigger - 2. We can compute the max time for the pipelines (which is how long the CI will take), and compute the 95th percentile over a certain time period. - 3. This will yield the time that the CI pipelines *usually* take. - -6. Build a model for the Markdown template & create the markdown. - 1. We'll need to process the data from the Kusto queries into models that we can format the markdown handlebars template. - 2. We'll also need to generate links to the Grafana dashboard for each queue. - 3. Use the model to turn the handlebars template into markdown. - 4. *It's also possible to include screenshots of the Grafana graphs to embed in the markdown. This should be noted and can be explored later.* - -7. Send the checkrun to GitHub. - 1. The markdown will not be refreshed whenever the user opens the page, instead it'll be a "one-shot" when the PR is submitted. - -### Proof of Concept (POC) - -See https://github.com/maestro-auth-test/helix-queue-insights-test/pull/11/checks?check_run_id=6801735696 - -### Risk - -- Will the new implementation of any existing functionality cause breaking changes for existing consumers? - - No. This is a new feature that will not cause any breaking changes for our customers. -- What are your assumptions? - - The design mockup and the information it provides are beneficial to our customers and will allow them to get a high level overview of relevant information about the status of Helix. -- What are your unknowns? - - The level of satisfaction this will bring to our customers. - - The accuracy of our data. Queue behavior can wildly fluctuate between outages and large test runs. Ideally we should have a system to track the accuracy of our predictions to have the data for our customers and improve our estimates. -- What dependencies will this epic/feature(s) have? - - Kusto - - AzDo - - Grafana - - GitHub - - Matrix of truth -- Is there a goal to have this work completed by, and what is the risk of not hitting that date? (e.g. missed OKRs, increased pain-points for consumers, functionality is required for the next product release, et cetera) - - August 2022. I've limited my scope of features I will work on to be able to deliver complete features. -- Does anything the new feature depend on consume a limited/throttled API resource? - - While not throttled, the preview pipeline API can take ~10 seconds to return. - - While we are only planning this check run to be a snapshot when the PR is created, GitHub gets grumpy when updating a checkrun numerous times. -- Have you estimated what maximum usage is? - - No, but this type of feature is already implemented, and this project will extend off that. -- Are you utilizing any response data that allows intelligent back-off from the service? - - All API calls in this project are returned synchronously. -- What is the plan for getting more capacity if the feature both must exist and needs more capacity than available? - - This feature shouldn't require more capacity than I can handle. - - If so, we can reduce the scope of the project and prioritize key items we think would be beneficial to the customer. - -### Usage Telemetry - -- How are we measuring the “usefulness” to the stakeholders of the business objectives? - - After the feature is implemented and initial rolled out, we can ask members of the CI Counsel if the feature is providing usefulness, and things we can change to make it more useful. - - We will also use the sentiment tracker from [`Helix.Utility.UserSentiment`](https://dev.azure.com/dnceng/internal/_git/dotnet-helix-service?path=/src/Utility/Helix.Utility.UserSentiment) to gather information on whether the new information is helpful. -- How are we tracking the usage of this new feature? - - The same way we track who has enabled .NET Build Analysis - - There is currently no plan on tracking how many people have actually viewed this new checkrun. - -## Service-ability of Feature - -If the format of our database changes, we'll have to change the Kusto queries that this feature uses. In that event, our Grafana dashboard will also be broken. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cincrease-visibility-helix-queues-arcade8824.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cincrease-visibility-helix-queues-arcade8824.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cincrease-visibility-helix-queues-arcade8824.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/moving-daily-builds-off-dotnetcli-arcade5757.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/moving-daily-builds-off-dotnetcli-arcade5757.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/moving-daily-builds-off-dotnetcli-arcade5757.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/moving-daily-builds-off-dotnetcli-arcade5757.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,170 +0,0 @@ -# Moving daily builds off the dotnetcli storage account (Related to [Arcade epic #5757](https://github.com/dotnet/arcade/issues/5757)) - -## Summary - -Prior to .NET Core 6.0, all internally-produced daily builds of .NET Core were Authenticode "real" signed and published to the same storage locations used for official releases of .NET Core. In latest builds, the same publishing locations continue to be used but non-final builds are not real-signed. - -While publishing to a single location is convenient for the infrastructure, it poses multiple risks. Every build that publishes ends up handling secrets that allow writing to the official storage account, making these builds sensitive security-wise. These builds' publishing infrastructure could also simply contain bugs and accidentally overwrite blobs they should not, or delete them simply by accident. To prevent this we should move daily builds onto a storage account (possibly in a different subscription entirely, but this is not necessary), and secrets that relate to publishing to the production account can be removed from almost all locations and be made available to a select few Azure DevOps build pipelines, and only when publishing real assets. - -As this will be a disruptive change, something to do at the same time that can improve safety would be to start using [the Azure Storage "Legal Hold" flag](https://docs.microsoft.com/en-us/rest/api/storagerp/blobcontainers/setlegalhold) for all containers with .NET Core builds in them. In experiments, this easily-reversed container-level change allows adding new blobs but blocks deleting or modifying existing ones. This means an extra layer of protection both from engineering errors (e.g. accidental overwriting, accidentally duplicating content) as well as meaning malicious users obtaining storage account credentials could not modify existing assets to contain their payloads. - -## Stakeholders - -- .NET Core Engineering Services team (contact: @dnceng) -- .NET Core SDK team (contact: @marcpopMSFT) -- .NET Core Docker team (contact: @MichaelSimons / @mthalman): The .NET Docker team produces Docker images on mcr.microsoft.com that preinstall .NET Core SDKs and may need to adjust this. -- .NET Install Scripts team (contact: @vlada-shubina / @BOzturkMSFT): If a new location for .NET Core SDKs/ Runtimes is introduced, the install scripts should support this directly for non-official build -- .NET Core PM Team (contact: @richlander) : Any change in the source of our runtimes / SDKs will need to have a public annoucement letting users know this is coming, as there are some number of external customers using daily builds in their existing development / CI process. -- .NET Core Runtime teams (various; most other than SDK will uptake any change in behavior from Arcade.) -- .NET Core Release team (contact: @leecow https://github.com/dotnet/release) -- Anyone using dotnet-install to download daily bits - -## Risk - -- What are the unknowns? - - We don't know outside common Arcade functionality where all dependencies on these storage account URIs are located. There will almost certainly be a multi-week tail of folks finding their build or dev workflow broken and pinging us from inside and outside the .NET org. - - We actually don't know how many users actually use dailies (following up with @RichLander) - - We don't know all the places where usage of dotnetcli URLs is hard-coded in calculating download urls, but there are definitely plenty. - -- Are there any POCs (proof of concepts) required to be built for this epic? _(note I used the "epic" one-pager; this might not be an epic)_ - - There is nothing super groundbreaking about having multiple locations that one might find a build in, with a preference hierarchy for when the same content is found in more than one location. That said, here are some things I imagine we might want to try rigging up versions of the dotnet-install.sh / ps1 scripts that start looking in dotnetcli, then fall back automatically to the new build storage account (named something like "dotnetbuilds") to get feedback on whether this is satisfactory. - -- What dependencies will this epic have? Are the dependencies currently in a state that the functionality in the epic can consume them now, or will they need to be updated? - - None - it's new work to improve how and when we access secrets that expose our sensitive assets are, along with making it clear via simple base URI that a build is not official. - -- Will the new implementation of any existing functionality cause breaking changes for existing consumers? - - This change guarantees there will be some breaking changes for users who miss public communication about the changes to backing storage for .NET Core SDKs/Runtimes. I would alse expect to see some problems just arising from bugs and needing to run the "machinery" of our build/release project to catch all the places that this occurs. This can be mitigated in the short term by continuing to do the old behaviors (e.g. sign all binaries and publish to both locations) until we feel confident in the content being uploaded to the new storage account. - -- Is there a goal to have this work completed by, and what is the risk of not hitting that date? (e.g. missed OKRs, increased pain-points for consumers, functionality is required for the next product release, et cetera) - - Aside from the challenges listed, there is no specific date or milestone this work must be completed within. It should be done as quickly as safely possible for its benefits but is not tied to any particular milestone or product release date. - -## Open questions - -- Is this actually in the Unified build access epic? (dotnet/arcade#5757) - - I don't know, but I'm at least writing this one-pager as part of that epic. - -- Do we actually want external users to have access to unsigned .NET Core SDKs? - - Per discussion with @mmitche: "Yes; we're (implicitly) saying that MS has not made a statement about the quality or validity of it, and if the user (say, Bing.com) wants to ship it they have to sign it" - -- Same question, but do we want unsigned SDKs building official builds? - - Per discussion with @mmitche, it's unavoidable in certain cases, and while ideally we'd only use offically released previews, Barry says this is not a problem" - -- How can we actually determine how many external / internal users are using dailies? - -- Who owns the SDL validation and the new CDN that would likely be needed here (note: a sufficiently geo-redundant storage account can likely handle this performance-wise) - -- Is there an SDL Threat Model for dotnetcli? - - Nothing since 2016. It is likely something we must address. - -- Where should these resources live, and who should own them? - -## Components to change, with order/estimates of work to do - -### Component: dotnet-install scripts - - As these scripts are the most common entry point for installing .NET Core SDKs and runtimes, including for most DncEng infrastructure, updating these scripts would be the first step such that as soon as builds start publishing bits there, these scripts would continue to work as expected. Estimate: ~1 week. - - #### High-level activities: - - Secure and create the storage account to use (note "dotnetbuild" is taken, perhaps "dotnetbuilds" or one of our pre-existing accounts like "dotnetbuildoutput") - - Insert secrets for the new storage account into EngKeyVault for usage by builds. - - Copy over (and munge versions to prove things worked) some builds from the DotNetCLI storage account into this account - - Modify scripts in https://github.com/dotnet/install-scripts until they can correctly install from the new location (with preference "DotNetCLI -> New Storage account --> Version specified on command line). - -### Component: dotnet/arcade repo - - Shared functionality for fetching the dotnet-install scripts, as well as the "where to publish" logic lives in https://github.com/dotnet/arcade. This makes an obvious next place to address. Additionally, we could bring back logic created earlier to have dotnet-install.sh/ps1 in the eng/common folder of Arcade, allowing Arcade-ified repositories to use the latest script changes before any chance of impacting all other users publicly, or have a secondary location supported for the dotnet-install scripts. - - #### High-level activities: - - Add variable group(s) containing secrets for publishing to the new storage accounts - - Introduce the ability to publish daily builds to the new storage account consuming values from these variable groups. - - Update mentions of storage accounts in documentation to reflect changes. - - Remove existing referneces to dotnetcli-storage-key variables - -### Component: Partner teams (dotnet/sdk, docker, and other repos - - The .NET Core SDK team (and possibly others; we would search through all existing variable usage in their main builds and get a list) would need to make any related changes related to acquiring bits. - - #### High-level activities: - - Work with SDK team to ensure relevant components know about possible new locations for bits. - - Scan for all uses of dotnetcli in the main dev branches and start triaging them (understand and convert or update usage) - - Work with Docker team to understand how they acquire .NET core installations for Docker image creation; this would likely just be a change to the infrastructure that finds URLs and calculates hashes of the bits to be installed in docker images, and ensuring it knows about the new account. - -### .NET Engineering Services tasks - - Aside from being responsible for driving this overall effort and all dotnet/arcade changes, the .NET Core Engineering Services team would be responsible for cleaning up after daily builds were migrated: - - #### High-level activities: - - Remove entirely (or limit the pipelines allowed to use it) the DotNet-DotNetCli-Storage and DotNet-MSRC-Storage variable groups from dnceng.vs and devdiv.vs. - - Remove all dotnetcli/dotnetclimsrc secrets from EngKeyVault and cycle storage keys (after dailies are working for some time) - - Rig up the ability for official release pipelines to continue to publish to the "official" storage account. - - Set Legal Hold flag on all real containers used for builds and make sure documentation reflects this (along with "how to break out of this temporarily if needed" docs) - - Updates for publish destinations (i.e.) (PublishingConstants.cs). Change the target storage accounts to the dotnetbuildoutput/dotnetbuilds account. - -## Serviceability - -- How will the components that make up this epic be tested? - - Where functionality is changed in a repository, unit / scenario testing will be added. Most of the work is high-level configuration of Azure DevOps and builds so the testing will running the machinery in question. For the end-to-end "full .NET Core SDK is produced and uploaded to the new account" scenario, this will require a manual test plan to ensure that at least the happy-path works before turning functionality on. - -- How will we have confidence in the deployments/shipping of the components of this epic? - - We'll know things have successfully moved over when we disable / delete the secrets related to the dotnetcli storage accounts from shared key vaults, cycle these values, resolve outstanding issues, and continue to function. - -- Identifying secrets (e.g. PATs, certificates, et cetera) that will be used (new ones to be created; existing ones to be used). - - Storage Account Keys (used for publishing), rotated via Azure Portal - - Storage account container-specific SAS tokens, generated programmatically or via Azure Portal - -- Does this change any existing SDL threat model? -- Does this require a new SDL threat model? - - The most recent threat model I am aware of dates back to 2016 and was written before Azure DevOps public support existed. While this change is largely meant to improve security, it's clear this area is due for some SDL review regardless of this change. - - -### Rollout and Deployment -- How will we roll this out safely into production? - - This may be difficult; it's impossible to know everything you don't know. However, making the storage-account choice conditional on build type and enabling it a little bit at a time make it relatively simple to undo if major problems are hit. Additionally, publishing to both locations in the beginning may represent the simplest way to proceed. This would be temporary, to allow us to rapidly switch back in case of problems. - - - Are we deprecating something else? - - No, all previously defined storage accounts for SDKs/Runtimes continue to exist, they only get fewer and more meaningful insertions, along with making blobs inherently immutable via the "Legal Hold" feature. - -- How often and with what means we will deploy this? - -Once adopted these changes are enshrined in the DotNet Arcade publishing workflow, so changes are deployed to where they're used via regular Arcade dependency flow pull requests. - -- What needs to be deployed and where? - - New storage accounts will be added to a subscription (TBD; this subscription will likely need to be treated "special"). Everything else comes from changes to Azure DevOps pipelines and the dotnet/arcade repo. - -- What are the risks when doing it? - - While change is ongoing, access to daily builds, the ability to produce new release builds, and .NET Core repositories' official pipelines may be broken for some time. - - -## Usage Telemetry -- How are we tracking the “usefulness” to our customers of the goals? - - As this is an improvement for both security and reliability, we don't care how useful these changes are for users, just that they can still perform their builds through some means. - -- How are we tracking the usage of the changes of the goals? - - Usage metrics would be based off the existing dotnet install telemetry (and likely this is where we'd have to go to know who is using non-release builds). - - -## FR Hand off -- What documentation/information needs to be provided to FR so the team as a whole is successful in maintaining these changes? - -Changes to the publishing workflow, specifically the components inside dotnet/arcade, will need to be detailed and stored in the wiki or documentation folders of dotnet/arcade. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cmoving-daily-builds-off-dotnetcli-arcade5757.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cmoving-daily-builds-off-dotnetcli-arcade5757.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cmoving-daily-builds-off-dotnetcli-arcade5757.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/native-tools-bootstrapping-security15522.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/native-tools-bootstrapping-security15522.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/native-tools-bootstrapping-security15522.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/native-tools-bootstrapping-security15522.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,101 +0,0 @@ -# Deprecating Native Tools Bootstrapping - -## Purpose -The whole thrust behind native tools bootstrapping was that build machines should be -clean and repos should bring their dependencies to the machines at build times. However, -this presents a major security problem: how do we ensure that all these repos are keeping -the executables they're installing on the build machines up to date? The current answer -is that we simply aren't, but that needs to change. - -Going forward, **we will be installing all artifacts on the build machines directly via -dotnet-helix-machines** (the machine prep process), except for the ones installed via package -feeds. This is a departure from the "keep build machines clean" philosophy and from our work -with folks on bootstrapping native dependencies, but it makes sense from a security and -maintainability perspective. - -## The Current State of the World -Based on [a search of the dotnet org](https://github.com/search?l=JSON&q=org%3Adotnet+native-tools&type=Code), -there are currently 10 non-archived repos using native tools bootstrapping. The artifacts -they're bootstrapping are as follows: - -| Artifact | Version(s) | Latest Patch Version(s) | Repos Using | -|----------|------------|-------------------------|-------------| -| cmake | 3.11.1, 3.14.5, 3.16.4, 3.21.0 | 3.11.4, 3.14.7, 3.16.9, 3.21.5 | arcade-validation, deployment-tools, installer, msquic, runtime, winforms | -| cmake-test (fake artifact for testing) | 3.11.1 | N/A | arcade-validation | -| dotnet-api-docs_netcoreapp3.0 | 0.0.0.2 | N/A | wpf-test | -| dotnet-api-docs_net5.0 | 0.0.0.3 | N/A | winforms, wpf | -| msvcurt-c1xx | 0.0.0.8 | N/A | wpf-test | -| net-framework-48-ref-assemblies | 0.0.0.1 | N/A | wpf, wpf-test | -| perl | 5.32.1.1 | 5.32.1.1 | fsharp | -| python3 | 3.7.1 | 3.7.12 | deployment-tools, performance, runtime | -| strawberry-perl | 5.28.1.1-1 | 5.28.2.1 | wpf, wpf-test | - -This indicates that four main executables are being bootstrapped: -* CMake (multiple different minor versions) -* Perl -* Python 3 -* Strawberry Perl - -Additionally, Winforms and WPF rely on some zip files that probably should come directly from -framework targeting packs, etc. - -Evidence of the necessity of this work can be found in the fact that Python 3.7.1 has -[multiple CVEs](https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=python+3.7) against it. - -## Removing Native Tools Bootstrapping Direct Downloading -The most straightforward way to deal with this problem is to simply stop natively bootstrapping artifacts -onto the machines. This approach has two main advantages: first, it brings the security of the machines fully -under the purview of the .NET Engineering Services team (which makes sense as we manage the machines -in every other respect) and allows us to make sure these artifacts are kept up-to-date, and second, -it simplifies the task of maintaining these artifacts for vendors (as there will be one central repository -for the artifacts and one place to check for component governance alerts). - -It also has its drawbacks, however, which will be mentioned later in the document. However, it is the -belief of the .NET Engineering Services team that these drawbacks do not outweigh the benefits of -consolidating artifacts in dotnet-helix-machines. - -The basic outline of this process would be: - -1. Take stock of what native tools are currently being used, create artifacts for them, and install - and add those artifacts to the build queues. -2. Alert teams that we will be decommissioning native tools bootstrapping and installing tools like - CMake and Perl on the build machines and give them time to migrate their builds to not use - native tools bootstrapping anymore. -3. Rework native tools scripts in Arcade to put the appropriate version of an artifact on the machine - (e.g., a specific minor version of CMake) on the path. - -In the future, when a team needs a specific artifact, they can request it be added to the machines. We will -vet the artifact through component governance before adding it to machines in production. If the artifact -is going to be installed side-by-side along with other versions of the artifact (e.g., if they want to install -CMake 3.21 and 3.11 is already on the machines), the requester will specify the version they want to use -in the global.json under "native-tools" just like they do today. The bootstrapping scripts will then elevate -the specific version of a tool to the path. However, when possible, we should attempt to consolidate on a -single version to obviate the need for the use of these scripts. - -### Takebacks/Downsides -This solution is, unfortunately, a takeback from previous solutions that involved investment in native -tools bootstrapping (see our epic for [Harmonizing Arcade and Runtime Repos](https://github.com/dotnet/arcade/issues/6560)). -Most obviously, this worsens the local dev experience which previously relied on native tools bootstrapping -to make specific dependencies less necessary. Going forward, repos will instead have to specifically note -which dependencies are required to build the product in their readme or COMPILING.md. The .NET Engineering -Services team will send out reminders to update the versioning of these dependencies every time there is a -version bump of an artifact. Eventually the plan is to use the matrix of truth (which knows which versions -of artifacts are installed on images) to flow the exact versions being used on build machines to READMEs automatedly. - -This also reduces the control that repos have over their dependencies. The hope is that by allowing for side-by-side -installation and minor version selection, repos will still be able to have control over minor versions of -the tools they use while staying up to date with the latest patches. - -In order to avoid build breaks while still maintaining security, once we have settled on a minor version for an artifact, -we will only be bumping its patch version as that is updated. This will restrict artifacts to security updates and bugfixes, -minimizing build breakage. The only time when a minor or major version bump will be required is when a particular version -reaches end-of-life and thus will no longer be maintained. - -## Conclusion -While this is a significant departure from our previous thinking in the space, deprecating direct downloads via -native tools bootstrapping and moving to a machine-only artifacts world will significantly improve the -security and maintainability of machine artifacts going forward. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cnative-tools-bootstrapping-security15522.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cnative-tools-bootstrapping-security15522.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cnative-tools-bootstrapping-security15522.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/non-sdk-partial-releases-core-eng-14577.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/non-sdk-partial-releases-core-eng-14577.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/non-sdk-partial-releases-core-eng-14577.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/non-sdk-partial-releases-core-eng-14577.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,131 +0,0 @@ -# Non-SDK Partial Releases for 6.0 - -## Summary - -In general, we only have two types of releases: full releases (which consist of sdk, runtime, aspnetcore, and windows desktop artifacts) and sdk-only releases (which only consist of the sdk, and rely on previously released versions of runtime, aspnetcore and windows desktop). However, in the 6.0 timeframe, we now find ourselves requiring a non-sdk partial release (i.e. a runtime-only or runtime and aspnetcore-only release), due to requirements for MAUI. Specifically, we will need to publish all of the workload packs and packages for this process. - -## Stakeholders - -* .NET Core Engineering Services team (contact: @dnceng) -* .NET SDK team -* .NET MAUI team -* .NET Release team - -## Risks - -* What are the unknowns? - * Today, it is unclear how to identify which files we will want to publish and how. Many are in the "workloads" directory for a given repo (runtime, emsdk), but the nupkgs are all in a flat directory structure. While we could publish all of the new nupkgs, it's unclear if we would actually want to. The safest option is to just publish all of the artifacts that are part of the release, rather than trying to pick and choose. This is what we should do, as it is simpler, safer, and gives us more flexibility for using this work in the future. - * We do not know how much of the infrastructure around releases relies on there being an actual sdk version associated with a release. Depending on how much we can work around, we may be able to work within the current infrastructure, but it also might require a full rewrite of some of the initialize release work, including that around linux signing, building the release layout, generating the release metadata (if we want to do that, which is also an unknown), etc. - * We do not know how much of the full release process we will require in a runtime-only release, and how that will interact with our scheduled releases. We want to make sure that we reuse and share as much of the current infrastructure and processes as we can; however, it may require some one off work that would only work for these non-sdk partial releases. - -* Are there any POCs (proofs of concept) that need to be built for this work? - - There is nothing particularly new that we should need to build for this work that doesn't already exist as part of the Stage-DotNet and Release-DotNet-5.x pipelines. While we will require some modifications to the current system that may necessitate a new pipeline on the release side, the staging pipeline should be able to be used mostly, if not completely, unchanged. - -* What dependencies will this work have? Are the dependencies currently in a state that the functionality in the work can consume them now, or will they need to be updated? - - This work depends on the work that the runtime, emsdk and other repositories have done to allow for releases of workloads (namely, the workloads directory that these repositories now produce). The work may also rely on a new naming schema for the workload nupkgs, if we decide we need to do that. Finally, the work relies on the Stage-DotNet and Release-DotNet-5.x pipelines that currently perform our releases, and the processes that those pipelines do today. These pipelines and/or processes will likely need to be updated in order to support this new type of release. - -* Will the new implementation of any existing functionality cause breaking changes for existing consumers? - - It should not. Wherever possible, we should add existing functionality that is exclusive to non-sdk partial releases under option flags that do not affect existing infrastructure. - -* Is there a goal to have this work completed by, and what is the risk of not hitting that date? (e.g. missed OKRs, increased pain-points for consumers, functionality is required for the next product release, et cetera) - - This work is required for MAUI to release pre-.NET 7. What will trigger the need for this work is a big runtime change that is too risky to immediately go into .NET 6 GA or servicing, but is required for MAUI. We will likely need this by early 2022. While this work is currently specifically for MAUI bring up today, it may likely be needed for shipping preview versions of servicing releases of .NET 6 with potentially risky changes. - -## Open Questions - -* What epic should this be part of? - - It is strongly recommended that this work go into its own epic - -* What needs to be updated? - - It's unclear, as the staging pipeline fails early (in linux signing), and everything later in the pipeline is gated on the signing stage. Once we have a clear picture of the staging pipeline, we can get a handle on what changes need to be done in the release pipeline. - -* Will this be treated differently than a full release or an sdk-only release? - - Probably, as we aren't going to be publishing everything from the drop, unlike what we do for full/sdk-only releases. - -## Components to change, with order/estimates of work to do - -### Component: Staging pipeline - -The Staging pipeline does the work of preparing the release, including gathering the drop, signing the assets, validating the assets, and creating the final release layout. Most of the pipeline is pretty robust and seems to handle well builds that don't contain sdk assets, however some of the pipeline relies on a fully formed release config.json, which we will not have in these releases. - -#### High-level activities: - -* Update the config file generation to allow us to get runtime version info from the runtime assets (and aspnetcore version from the aspnetcore assets), not just the sdk dependencies. We would like to, as much as possible, automatically detect what kind of release the pipeline has been asked to perform. -* Update initialize-release to allow for config files that are missing versions for sdk, runtime, aspnetcore, etc. -* Determine if we actually need the release metadata, or if that will be unnecessary for this sort of release. -* Modify the yaml to skip anything that is unnecessary for these releases -* Find all the places where we use the sdk version in the yaml, and for these releases, use something else (runtime version, for example). Using the sdk version is currently breaking some publishing code, in addition to the release initialization infrastructure. -* Confirm that VS insertion continues to work properly for this scenario - -### Component: Release pipeline - -The release pipeline does the actual work of publishing the release, including creating additional metadata files, publishing nuget packages, publishing signed files to dotnetcli, updating aka.ms links, publishing symbols, publishing transport files to public locations, etc. - -#### High-level activities: -* Walk through the entire pipeline to see what uses the config file, and how. -* Confirm that anything that uses the release layout (i.e., what we drop to NET_CORE on vsufile), can handle files being missing. -* Update whatever metadata file creation there is to accept a config file that is missing data that is not part of the release. -* Confirm that repo-propagation will handle missing versions gracefully. -* Determine how we will release only the workloads directory, rather than the entire drop. -* Determine how we will identify and publish only those nupkgs that we are interested in releasing for this process. - -### Component: Product repositories - -The product repositories need to make sure that any workloads that need to be released as part of this process are published as part of the workloads directory. Many already do this today. - -#### High-level activities: -* Make sure any repositories we need to be part of this work publish required assets to their workloads directories. -* Where possible, have assets put in a subdirectory that identifies the sdk release version the assets should be associated with -* Potentially rename required nupkgs to identify them as Workload packages, like we do the VS.* packages for other releases. - -## Serviceability - -* How will the components that make up this epic be tested? - -Like the staging pipeline, we will have a test pipeline and BAR Build ID that we can run through the full pipeline for testing. This should be run prior to any main testing and will run on production rollouts. Additionally, all new functionality will have unit tests added to test that they're doing what they are supposed to and don't break prior behavior. - -* How will we have confidence in the deployments/shipping of the components of this work? - -By using the test pipelines prior to deployment. - -## Rollout and Deployment - -* How will we roll this out safely into production? - -By using the test pipelines with known good builds. - -* How often and with what means will we deploy this? - -Weekly, along with the rest of dotnet-release, or as needed, if weekly is too frequent. - -* What needs to be deployed and where? - -Any code related to the staging and release pipelines. We will deploy to the production branch (and potentially later, the release/6.0 branch) from main. - -* What are the risks when deploying? - -We may break the main pipeline for full releases if we do not do enough testing. With two/three different scenarios that require testing, we may need to run the full test pipeline for each of these types of releases. Instructions for validating any changes made to the pipeline can be found [here](https://github.com/dotnet/arcade/blob/main/Documentation/Staging-Pipeline/making-and-validating-changes.md). - -## FR Handoff - -* What documantion/information needs to be provided to FR so the team as a whole is successful in maintaining these changes? - -Changes to the staging pipeline are already documented in the Staging-Pipeline docs, however, we will likely want to add additional information about the new scenarios. - -## Useful Release-Related Documentation and Links - -* [Stage-DotNet](https://dev.azure.com/dnceng/internal/_build?definitionId=792) -* [Release-DotNet-5.x](https://dev.azure.com/dnceng/internal/_build?definitionId=984) -* [Running the Staging Pipeline](https://github.com/dotnet/arcade/blob/main/Documentation/Staging-Pipeline/running-the-pipeline.md) -* [Original Release Rings Plan](https://github.com/dotnet/arcade/blob/main/Documentation/ReleaseRingsPlan.md) -* [releases.json](https://github.com/dotnet/core/blob/main/release-notes/6.0/releases.json) - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cnon-sdk-partial-releases-core-eng-14577.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cnon-sdk-partial-releases-core-eng-14577.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cnon-sdk-partial-releases-core-eng-14577.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/pipeline-machine-learning-arcade8824.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/pipeline-machine-learning-arcade8824.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/pipeline-machine-learning-arcade8824.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/pipeline-machine-learning-arcade8824.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,158 +0,0 @@ -# Predicting Pipeline Durations - -Our customers would love to know how long their CI pipeline takes. Ideally, we would want to build a model that can account for the current state of Helix, the work items they send, the congestion of AzDo, etc. to get a decent accurate. Unfortunately, this is a highly complex model that is quite difficult to build (but theoretically possible). - -Instead, we can build a model to give us a range of estimates, based on the past durations of their pipelines. - -We have tons of historical data that can enable us to make predictions and generate confidence intervals on the length of CI pipelines. - -## Stakeholders - -* Our Partners (@dncpartners) -* .NET Engineering Services (@dnceng) - - -## Proof of Concept - -*[Here is a jupyter notebook](https://ml.azure.com/fileexplorerAzNB?wsid=/subscriptions/a4fc5514-21a9-4296-bfaf-5c7ee7fa35d1/resourcegroups/t-jperez/workspaces/HelixMLTest&tid=72f988bf-86f1-41af-91ab-2d7cd011db47&activeFilePath=Users/t-jperez/pipeline-machine-learning-arcade8824.ipynb) with the detailed statistics and data science that supports the following claims.* - -By plotting a histogram of pipeline durations for specific pipelines, I've determined that they seem to follow distributions that we can model. For instance, here is the `roslyn-CI` pipeline, with a dweibull distribution fitted. - - - -With this distribution, we can compute the 95% confidence interval, which for this pipeline, is: - -`1:11:27 +- 0:27:57` - -We can vary the confidence interval, and thus the accuracy of our predictions, for a smaller range. From testing on all pipelines that have Build Analysis enabled, here are the detailed statistics for the *ranges* of predictions we give, in seconds. This data is back-tested, meaning that at the time the range was computed, the model only had the data available previously. - -``` -count 534.000000 -mean 69.490963 -std 415.535966 -min 3.197592 -25% 12.124280 -50% 19.041886 -75% 27.074249 -max 6289.076812 -dtype: object -``` - -### Model Accuracy - -With a target of 95% accurate, the back-testing concluded that we had a true accuracy of 93.3%. - -We backtested the model by training on all previous data before a point, and then testing on 1 week ahead, on data the model has not seen before. Here is a graph of the accuracy over time. - - - -The dashed red line shows the target, 95% accurate predictions. Our predictions hold accurate, at worst dipping to just below 89%, and hovering between 90% and 95%. - -This data is an aggregation of accuracy vs time for all repos with Build Analysis. When evaluating the accuracy, the data point in question was not used to fit the distribution, preventing a look-ahead bias. Accuracy is defined as: - -$\frac{\text{points in predicted range}}{\text{total points}}$ - -### Implementation - -A unique distribution for each pipeline requires a training for each pipeline, and this functionality would more than likely require an Azure Function. The added burden of maintenance and engineering machinery makes this too cumbersome. - -Instead, we can use [Chebyshev's inequality](https://en.wikipedia.org/wiki/Chebyshev's_inequality), because we have the mean and variance defined for each pipeline. Using ${\displaystyle k={\sqrt {2}}}$ shows that the probability that values lie outside the interval ${\displaystyle (\mu -{\sqrt {2}}\sigma ,\mu +{\sqrt {2}}\sigma )}$ does not exceed ${\displaystyle {\frac {1}{2}}}$. At the time of writing, here is a list of the ranges we'd give customers who have Build Analysis enabled: - - -| Definition | Mean | Confidence Interval | -| -------------------------------------------------- | ------------ | ------------------- | -| \dotnet\arcade\arcade-ci | 45m 14s | ± 10m 41s | -| \dotnet\arcade-services\dotnet-arcade-services | 25m 8s | ± 5m 26s | -| \dotnet\arcade-validation\arcade-validation-ci | 13m 24s | ± 6m 17s | -| \dotnet\aspnetcore\aspnetcore-ci | 1hr 32m 8s | ± 27m 56s | -| \dotnet\aspnetcore\aspnetcore-components-e2e | 33m 58s | ± 3m 11s | -| \dotnet\aspnetcore\aspnetcore-quarantined-pr | 1hr 4m 4s | ± 17m 7s | -| \dotnet\installer\installer | 57m 22s | ± 36m 3s | -| \dotnet\performance\performance-ci | 47m 41s | ± 22m 7s | -| \dotnet\roslyn\roslyn-CI | 1hr 10m 41s | ± 19m 22s | -| \dotnet\roslyn\roslyn-integration-CI | 1hr 22m 24s | ± 12m 23s | -| \dotnet\roslyn\roslyn-integration-corehost | 1hr 22m 56s | ± 11m 2s | -| \dotnet\runtime\dotnet-linker-tests | 59m 55s | ± 13m 55s | -| \dotnet\runtime\runtime | 2hrs 10m 52s | ± 1hr 21m 38s | -| \dotnet\runtime\runtime-coreclr outerloop | 4hrs 20m 24s | ± 57m 37s | -| \dotnet\runtime\runtime-coreclr superpmi-asmdiffs | 1hr 20m 26s | ± 14m 10s | -| \dotnet\runtime\runtime-coreclr superpmi-diffs | 1hr 53m 35s | ± 19m 24s | -| \dotnet\runtime\runtime-coreclr superpmi-replay | 1hr 38m 15s | ± 17m 26s | -| \dotnet\runtime\runtime-dev-innerloop | 1hr 21m 38s | ± 8m 16s | -| \dotnet\runtime\runtime-extra-platforms | 2hrs 23m | ± 52m 44s | -| \dotnet\runtime\runtime-libraries enterprise-linux | 41m 48s | ± 7m 57s | -| \dotnet\runtime-assets\runtime-assets | 2m 5s | ± 35s | -| \dotnet\sdk\dotnet-sdk-public-ci | 59m 1s | ± 16m 15s | - -We can use a Kusto Query like so and show this information in Queue Insights. (Here, I use Definition name for clarity, but I will use the DefinitionId in production) - -``` -TimelineBuilds -| where Project == "public" -| where Reason == "pullRequest" -| where TargetBranch == "main" -| where Result == "succeeded" -| extend PipelineDuration = datetime_diff('second', FinishTime, StartTime) * 1s -| project-keep Definition, PipelineDuration -| join kind=inner ( - TimelineBuilds - | where Project == "public" - | where Reason == "pullRequest" - | where TargetBranch == "main" - | where Result == "succeeded" - | project Definition, PipelineDuration = datetime_diff('second', FinishTime, StartTime) * 1s - | summarize - Bottom5 = percentile(PipelineDuration, 5), - Top95 = percentile(PipelineDuration, 95), - Count = count() - by Definition - | where Count >= 30) - on Definition -| where PipelineDuration between (Bottom5..Top95) -| summarize Mean = avg(PipelineDuration), ConfidenceInterval = sqrt(2) * totimespan(stdevp(PipelineDuration)) by Definition -``` - -*Thanks to Nikki ([@mathaholic](https://github.com/mathaholic)) for her help with this data analysis, and her idea to use Chebyshev’s Theorem!* - -### Caveats - -#### Multimodal Distributions - -There are some issues with this model. First, some pipelines, like `dotnet/runtime`'s have a multimodal distribution. This means we cannot accurately predict their pipeline duration. In this case, their distribution is multimodal because their first step, `Evaluate Paths` evaluates the changes in a given PR, and runs or skips different steps of their pipeline. - -We will hide the estimated time and instead inform the user that their pipeline cannot be predicted as it is too variable. This will also handle the case where the range of a CI pipeline exceeds the estimated time (*e.g.* `1min ± 5min`) - -#### Infrastructure Outages - -In addition, there is the issue of AzDo, Helix, or builds being on the floor, and we still give customers an estimate, blissfully unaware of any infrastructure errors. In the Juptyer notebook, I dive into an anomaly detection model, based on Helix work item wait times trying to predict this, but the model only improves accuracy by 0.3%. - - -For Helix and/or AzDo being on the floor, we will rely on our Known Issues infrastructure, and simply hide the checks when there are any critical infrastructure issues. - -## Risk - -* Will the new implementation of any existing functionality cause breaking changes for existing consumers? - * No, this will be a new feature. -* What are your assumptions? - * We'll constantly maintain data in the `TimelineBuilds` table. This feature depends on it. -* What are your unknowns? - * See above, how do we decompose multimodal distributions and become notified when services are down? -* What dependencies will this epic/feature(s) have? - * `TimelineBuilds` - * Kusto -* Are the dependencies currently in a state that the functionality in the epic can consume them now, or will they need to be updated? - * We can consume them now. -* Is there a goal to have this work completed by, and what is the risk of not hitting that date? (e.g. missed OKRs, increased pain-points for consumers, functionality is required for the next product release, et cetera) - * Aug 12, the end of the internship. -* Does anything the new feature depend on consume a limited/throttled API resource? - * No. -* Have you estimated what maximum usage is? - * No, but it wil be the same as the Queue Insights project. -* Are you utilizing any response data that allows intelligent back-off from the service? - * We only query Kusto, so there is no need for back-off. -* What is the plan for getting more capacity if the feature both must exist and needs more capacity than available? - * This feature wont require any additional capacity. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cpipeline-machine-learning-arcade8824.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cpipeline-machine-learning-arcade8824.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cpipeline-machine-learning-arcade8824.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/pr-failure-tagging-one-pager-core-eng-12136.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/pr-failure-tagging-one-pager-core-eng-12136.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/pr-failure-tagging-one-pager-core-eng-12136.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/pr-failure-tagging-one-pager-core-eng-12136.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,169 +0,0 @@ -## Automated GitHub tagging for failed Pull requests from Maestro++ dependency flow - -### Limitations: - -- This functionality only makes sense when the subscriptions involved are non-batched, and documentation will call this out. We only want to tag a partner team if we're fairly sure that it's worth someone from the source repo to take a look. -- We won't be able to detangle whether the root cause of a failure is the target repo build's flakiness, an intentional breaking change from the source repo that was advertised via email, or a break from changes that lack test coverage from the source repository, so a list of these posssibilities will be part of the message where source repos get tagged. - -See Epic: https://github.com/dotnet/core-eng/issues/12136 - -### Stakeholders - -While we hope that the features added in this epic provide benefit to any "target" repo of dependency flow, the primary stakeholder currently is the .NET SDK team. They exist at the end of the metaphorical dependency-flow river, so they experience potential issues with dependency updates from every layer in the stack, possibly in a synergistic fashion (i.e. the regression requires two or more repos' changes). - -### Risk - -- What are the unknowns? - - Unknowns are minimal for the work as described; the main being simply "will this improve how long it takes to look at a PR failure by the source repository of the build change? - -- Are there any POCs (proof of concepts) required to be built for this epic? - - No. This will be adding functionality to existing components that already do most of the related behaviors; after review it looks like every challenging thing done already has some component that could be augmented to support this. - -- What dependencies will this epic have? Are the dependencies currently in a state that the functionality in the epic can consume them now, or will they need to be updated? - - None. All work will occur in the arcade-services repo (unless we decide to store the mappings in another) - -- Will the new implementation of any existing functionality cause breaking changes for existing consumers? - - No breaking changes; the idea is simply to add logging and tagging to an existing semi-mature process. - -- Is there a goal to have this work completed by, and what is the risk of not hitting that date? (e.g. missed OKRs, increased pain-points for consumers, functionality is required for the next product release, et cetera) - - Goal is to finish by end of April 2021. Slipping the date has no associated risk, just means that the SDK team and other "end-of-the-line" repos will continue to operate as they are currently for some time. - -### Serviceability - -- How will the components that make up this epic be tested? - - Add tests to existing unit tests in the area (expanding on existing) - - Arcade-services scenario test. (Tests using checks already exist, will be an exercise in code reuse for this somewhat similar scenario.) - -- How will we have confidence in the deployments/shipping of the components of this epic? - - Regularly run scenario and unit tests that exercise this code path always run before deployment to production. - -- Identifying secrets (e.g. PATs, certificates, et cetera) that will be used (new ones to be created; existing ones to be used). - - No new secrets will be needed by the change. - -- Does this change any existing SDL threat or data privacy models? (models can be found in [core-eng/SDL](https://github.com/dotnet/core-eng/SDL) folder) -- Does this require a new SDL threat or data privacy models? - - No; the only PII used will be GitHub aliases / tags and this will be augmenting existing functionality. - -- Steps for setting up repro/test/dev environments? - - Same as https://github.com/dotnet/arcade-services/blob/main/docs/DevGuide.md - -#### Rollout and Deployment - -This section left blank as this will be part of an arcade-services component. - -### Usage Telemetry -- How are we tracking the “usefulness” to our customers of the goals? - - Since there can be lots of reasons a PR is merged quickly or slowly, pure data metrics likely do not help here unless we somehow had many more data points. However, we can monitor: - - Feedback from SDK and other end-of-the-line repo team members - - Whether we observe incidence of an upstream repo team member taking action before the downstream team raises the issue with them. - -- How are we tracking the usage of the changes of the goals? - - - We already use Application Insights for telemetry. The idea here will be to start tracking the success rate % of PRs from a given repo to another. By simply writing down source, destination, and success of the PR build we'll be able to identify which teams flow problems out of their repositories. It should be straightforward to make a "top N list" of repos whose changes break others, and dig in to the data from there. - -### Monitoring - - Already covered by the Dependency Flow error processor and Grafana alerts. - -### FR Hand off -- What documentation/information needs to be provided to FR so the team as a whole is successful in maintaining these changes? - - The location of mappings for Repository URL -> GitHub Tag, and instructions to update these. - -### Description of the work: - -#### Components changed: - -- New function: TagSourceRepositoryGitHubContacts() - - Reads config file from below, ensure a message is created in the PR to come check out the failure. Will reuse as much as possible from existing GitHub helper functions in Pull Request Actor. - -- Changes to [PullRequestActor](https://github.com/dotnet/arcade-services/blob/main/src/Maestro/SubscriptionActorService/PullRequestActor.cs) - - NonBatchedPullRequestActorImplementation changes: - - Pass along a flag inside its overload of SynchronizeInProgressPullRequestAsync() to indicate that repo-tagging should occur (this can only happen in non-batched, since it's not feasible to detangle who broke what in a batched update) - - This is needed to ensure that UpdatePullRequestAsync() gets called to check - - PullRequestActorImplementation changes: - - UpdatePullRequestAsync() would check first if it was being called inside a non-batched PR actor, and if so it would also check the existing field for the interesting state (`InProgressPullRequest.MergePolicyCheckResult == MergePolicyCheckResult.FailedPolicies`) - - Before continuing to updating the PR with new commits it would fetch the `dependency-flow-failure-notifications.json` file described below (or just have it as a local asset in the Maestro++ service), check to see if any failure tagging is requested, and apply a comment to the issue with all unique tags which have been requested. - - - SynchronizePullRequestAsync already has an entry in [its switch statement](https://github.com/dotnet/arcade-services/blob/main/src/Maestro/SubscriptionActorService/PullRequestActor.cs#L424-L426), so we'd have a different actionresult added for this case. - - For the `case MergePolicyCheckResult.FailedToMerge:` entry, we'd add a new enum value to SynchronizePullRequestResult (e.g. "InProgressCanUpdateNeedsNotification") - - At this point SynchronizeInProgressPullRequestAsync calls the new function (TagSourceRepositoryGitHubContacts()) to ensure that a boilerplate message is pasted in with the tags from the config file. - - Telemetry changes: - - Send telemetry with source and destianation repos whenever a non-batched PR is created. - - Whenever we tag an issue due to failed checks, send additional piece of telemetry with the same information indicating failure - - Could just send once at the very end, but this might miss sending check failure telemetry on PRs that get manually fixed up to pass, or are manually merged. - -- Changes to allow users to self-service dependency-flow failure notifications: - - Introduce a new file, say `https://github.com/dotnet/arcade-services/blob/main/dependency-flow-failure-notifications.json` sample content: - -``` json - { - [ - // Example showing options for disabling and specifying particular channels - { - // Notify @dotnet/runtime-infrastructure for any runtime content coming from the .NET 6 channel - // targeted to the Sdk and ASP.NET Core teams where PRs fail. - "SourceGitHubRepoUrl": "https://github.com/dotnet/runtime", - "TargetGitHubRepoUrls": - [ - "https://github.com/dotnet/sdk", - "https://github.com/dotnet/aspnetcore", - ], - "NotificationSettings": [{ - "GitHubTagsToNotify": [ - "@dotnet/runtime-infrastructure" - ], - "Channels": [ - ".NET 6", - ] - }, - // Notify a different tag for servicing branches - { - "GitHubTagsToNotify": [ - "@dotnet/runtime-infrastructure-servicing" - ], - "Channels": [ - ".NET 5", - ".NET 3" - ], - // leave the ability to disable in case failures are expected - "Enabled": false - } - ] - }, - // Minimal example: always alert on all channels for PRs made from Nuget.Client -> Dotnet SDK - { - "SourceGitHubRepoUrl": "https://github.com/nuget/nuget.client", - "TargetGitHubRepoUrls": - [ - "https://github.com/dotnet/sdk" - ], - "NotificationSettings": [{ - "GitHubTagsToNotify": [ - "@nuget/nuget-infrastructure", - ] - } - ] - } - ] - } -``` - -#### Notes -- All subscriptions to GitHub PR failure tagging must be OK with the "source" repo. That is, target repositories causing failure notifications for teams that do not want these notifications will have them removed. A blurb to this effect will be part of the failure notification tag boilerplate message. -- See related feature request: https://github.com/dotnet/arcade/issues/7102 - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cpr-failure-tagging-one-pager-core-eng-12136.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cpr-failure-tagging-one-pager-core-eng-12136.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cpr-failure-tagging-one-pager-core-eng-12136.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/retrying-stages-staging-pipeline.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/retrying-stages-staging-pipeline.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/retrying-stages-staging-pipeline.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/retrying-stages-staging-pipeline.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,157 +0,0 @@ -# Retrying Stages to Address Errors in Staging pipeline - -## What is the current process? - -Today we must run the entire Staging pipeline if we encounter any errors in the pipeline. -For eg: When Package push fails, we need to rerun everything to fix the bug. - -There is no way to test the pipeline with fewer stages. Since most of the later stages are dependent on earlier stages that takes a lot of time to complete. -For eg: If we want to test only the Required Validation stage, we will need to run Prep and Signing. - -## Problems with current process: -1. Rerunning a single stage in the pipeline is not possible without executing the entire pipeline again. -1. Running only the stage where changes were implemented is not feasible. -1. Re-executing the entire process and testing consumes a considerable amount of time. -1. Staging pipeline is complicated to understand, we need a simpler process. - -## Different approach for staging pipeline: - -| Approach | Pros | Cons | -|----------|------|-------| -| 1. We add some parameters in the pipeline, and make staging pipeline download the pipeline assets uploaded in the Signing stage of build with build id 12345 in the new pipeline run. Since the assets are already signed we just run required validation and publishing stage only We will need build id of the successful build. We will use azure apis to download the pipeline artifacts |
  • Easier to test
  • We can pick and choose which stages to rerun
  • Relatively easy to implement
  • Release pipeline is doing something similar |
  • Seems primitive and hacky | -| 2. Pipeline should be intelligent enough, like if we rerun the pipeline with the same BarId, it should look up the data in timeline builds and if the stage was previously successful from previous runs it should just not do anything. Like here if prep ring was successful, so it would skip prep and go to signing. Signing was already successful so now it will skip this stage and just download assets from the build with build if 12345. Then eventually just run Required Validation and publishing stage |
  • Lot of code change in every stage
  • Timeline data is in Kusto and they sometime take time to show up in kusto so prone to errors(waiting can help) |
  • Need to capture details which build ran with which barid
  • Seems hacky
  • Adds non determinism based on time, availability of systems. | -| 3. Create a cache to cache the Signed assets, so next time we re run the pipeline for the same barID, We look for signed assets in the cache. The assets are hashed, so if there is no change in the bits, we do not have to run that stage. So in case required validation was previously successful for the signed assets hash then skip this stage.|
  • Lot cleaner |
  • We may be adding complexity to our current infra
  • Have to maintain cache
  • Need to add an explicit ovveride when we need to re-sign.
  • Does not save a lot of time in 7.0/8.0 where in-build signing reduces the time in the signing stage. | -| 4. Split the pipeline into 2
  • Anything that alters the artifacts ( Signing)
  • Validation and publishing |
  • .Net 9 we are moving towards moving signing (anything that alters/creates the build artifacts is going to be moved) to main builds, so by splitting this we will align to work with .Net 9 ( which is awesome!)
  • Making the pipeline lot less complex
  • We can introduce testing infra (inject DI in the second pipeline, publish to test vs prod containers)
  • Makes the whole pipeline simpler
  • If we want to fix something in the second pipeline we don't have to rerun the first one. So fixes are and flaky test reruns are lot faster and easier. |
  • Have to maintain 2 pipeline
  • BCM work has to be reevaluated.| - - -## Goal and Motivation - -After carefully analyzing the pros and cons of all the above approaches, we decided to go with splitting the pipeline. - -The proposal here is to split the Staging Pipeline into two different pipelines and add the ability to rerun stages. Thinking of this work as 2 part process. -#### Version 1 (V1): -1. First pipeline will contain anything that alters or generates the artifacts (Eg: Signing, SBOM generation). -1. Second pipeline will contain Validation and publishing to various storage accounts. -#### Version 2 (V2): -1. Ability to add rerun stages in the Second pipeline - -## Stakeholders: -1. Tomas team -1. Release team - -### Current Flow - -```mermaid -flowchart LR; - prep["Prep Ring \n ~30min"] --> prep_override[Prep Ring Override] - prep_override --> signing["Signing Ring \n ~50min"] - prep_override --> source_code_validation["Source Code Validation \n ~40min"] - source_code_validation --> source_code_validation_override[Source Code Validation Override] - signing --> required_validation["Required Validation \n ~1h"] - signing --> validation["Validation \n ~5h"] - required_validation --> sbom_generation["SBOM Generation \n ~20m"] - required_validation --> required_validation_override[Required Validation Override] - validation --> validation_override[Validation Override] - required_validation_override --> publishing_v3_signed["Publish Post-Signing Assets \n ~1h20m"] - required_validation_override --> post_signing_publishing["Publish Signed Assets \n ~1h30m"] - required_validation_override --> vs_insertion["VS Insertion Ring \n ~50m"] - vs_insertion --> vs_insertion_override["VS Insertion \n Override \n "] - vs_insertion_override --> test_team_sign_off["Waiting for \n Test team \n to Sign off \n "] - test_team_sign_off --> staging_ring["Staging Ring \n "] - source_code_validation_override --> staging_ring["Staging Ring \n ~1h10m"] - staging_ring --> staging_ring_override["Staging Ring \n Override \n "] - staging_ring_override --> sign_off_for_finalizing_release["Sign off for \nfinalizing \n the release \n "] - sign_off_for_finalizing_release --> finalize_staging_ring["Finalize \n Staging Ring \n "] - sign_off_for_finalizing_release --> publish_cit_validated_assets["Publish CTI \n validated assets \n 1h20m"] - finalize_staging_ring --> approve_publishing_dotnetcs_internal["Approve \n publishing to \n dotnetcsinternal \n "] - approve_publishing_dotnetcs_internal --> handoff_publishing_ring["Handoff \n Publishing Ring \n (dotnetcsinternal) \n "] - sbom_generation --> sbom_generation_override[SBOM Generation Override] -``` - -## V1: -### Splitting Staging Pipeline: - -Proposed implementation is splitting the pipeline into two pipelines. -#### 1. First pipeline: Stage-Dotnet-Prepare-Artifacts -The Stage-Dotnet-Prepare-Artifacts pipeline will contain stages that alters or generates new (e.g. SBOM) artifacts. -```mermaid -flowchart LR; - prep["Prep Ring \n ~30min"] --> prep_override[Prep Ring Override] - prep_override --> signing["Signing Ring \n ~50min"] - signing--> sbom_generation["SBOM Generation \n ~20m"] - sbom_generation--> kick_2nd_pipeline["Stage-Dotnet-Validate-Publish pipeline kick off \n ~20m"] - sbom_generation --> sbom_generation_override[SBOM Generation Override] -``` -#### 2. Second pipeline: Stage-Dotnet-Validate-Publish -The Stage-Dotnet-Validate-Publish pipeline will contain validation and publishing stages. -```mermaid -flowchart LR; - start["Queued \n"] --> validation["Validation ~5h"] - validation --> validation_override[Validation Override] - start--> source_code_validation["Source Code \n Validation \n ~40min"] - source_code_validation --> source_code_validation_override[Source Code \n Validation \n Override] - start--> required_validation["Required \n Validation \n ~1h"] - required_validation --> required_validation_override[Required \n Validation \n Override] - required_validation_override --> publishing_v3_signed["Publish \n Post-Signing Assets \n ~1h20m"] - required_validation_override --> post_signing_publishing["Publish Signed \n Assets \n ~1h30m"] - required_validation_override --> vs_insertion["VS Insertion \n Ring \n ~50m"] - vs_insertion --> vs_insertion_override["VS Insertion \n Override \n "] - vs_insertion_override --> test_team_sign_off["Waiting for \n Test team \n to Sign off \n "] - test_team_sign_off --> staging_ring["Staging Ring \n "] - source_code_validation_override --> staging_ring["Staging Ring \n ~1h10m"] - staging_ring --> staging_ring_override["Staging Ring \n Override \n "] - staging_ring_override --> sign_off_for_finalizing_release["Sign off for \nfinalizing \n the release \n "] - sign_off_for_finalizing_release --> finalize_staging_ring["Finalize \n Staging Ring \n "] - sign_off_for_finalizing_release --> publish_cit_validated_assets["Publish CTI \n validated assets \n 1h20m"] - finalize_staging_ring --> approve_publishing_dotnetcs_internal["Approve \n publishing to \n dotnetcsinternal \n "] - approve_publishing_dotnetcs_internal --> handoff_publishing_ring["Handoff \n Publishing Ring \n (dotnetcsinternal) \n "] -``` - -### Advantages of splitting pipeline: -1. In .Net 9 we are going to move signing to main build. By splitting pipeline we are going to be in alignment with that plan, meaning we can retire the first pipeline when time comes and only the second pipeline will be staging pipeline then. -1. Bug fix and testing in validation and publishing stages are lot faster. We do not have to wait for the build to be signed everytime we make a fix to validation/publishing stage or re-run flaky tests. -1. We can add ability to rerun to smaller subset of stages as compared to Stage-Dotnet pipeline. - -### Interface between the Stage-Dotne-Prepare-Artifacts Pipeline and Stage-Dotnet-Validate-Publish pipeline: - -The first pipeline Stage-Dotnet-Prepare-Artifacts (May be during the create SBOM stage or after) kicks off a build in the Stage-Dotnet-Validate-Publish pipeline. This is similar to what we have in maestro promotion pipeline. The Stage-Dotnet-Prepare-Artifacts is not dependent on the Stage-Dotnet-Validate-Publish pipeline to be completed. - -The second pipeline Stage-Dotnet-Validate-Publish pipeline downloads the signed build artifacts from the Stage-Dotnet-Prepare-Artifacts pipeline. - -One of the main reason for adding the trigger to kick off a the Stage-Dotnet-Validate-Publish from the Stage-Dotnet-Prepare-Artifacts pipeline is so that we don't have to manually kick off the Stage-Dotnet-Validate-Publish build after the first pipeline completes. Additionally the Stage-Dotnet-Validate-Publish can be kicked off manually too. It will use BarBuildID / BuildId combination to download the signed assets from the first pipeline. - -## V2: - -Rerruning stages in Stage-Dotnet-Validate-Publish pipeline: - -Say Publishing Signed Assets fails and validation is successful -1. We can add the ability to pick and choose the stages to run in the second pipeline. - -Here we skipped Validation stage altogether. Similarly we can can add ability to skip other stages in the pipeline. - - -```mermaid -flowchart LR; - start["Queued \n"] --> publishing_v3_signed["Publish \n Post-Signing Assets \n ~1h20m"] - start --> post_signing_publishing["Publish Signed \n Assets \n ~1h30m"] - start --> vs_insertion["VS Insertion \n Ring \n ~50m"] - vs_insertion --> vs_insertion_override["VS Insertion \n Override \n "] - vs_insertion_override --> test_team_sign_off["Waiting for \n Test team \n to Sign off \n "] - test_team_sign_off --> staging_ring["Staging Ring \n "] - staging_ring --> staging_ring_override["Staging Ring \n Override \n "] - staging_ring_override --> sign_off_for_finalizing_release["Sign off for \nfinalizing \n the release \n "] - sign_off_for_finalizing_release --> finalize_staging_ring["Finalize \n Staging Ring \n "] - sign_off_for_finalizing_release --> publish_cit_validated_assets["Publish CTI \n validated assets \n 1h20m"] - finalize_staging_ring --> approve_publishing_dotnetcs_internal["Approve \n publishing to \n dotnetcsinternal \n "] - approve_publishing_dotnetcs_internal --> handoff_publishing_ring["Handoff \n Publishing Ring \n (dotnetcsinternal) \n "] -``` - -#### Risks: -1. We are dissecting the staging pipeline, there is a chance we might be messing up publishing to correct storage containers. -1. Since we will retire the old pipeline only after the new pipelines Stage-Dotnet-Prepare-Artifacts and Stage-Dotnet-Validate-Publish are up and running, there may be an extended period where we need to maintain extra pipelines. - -#### Additional information: -1. Verify we don't duplicate publishing of assets is tracked in (issue)[https://github.com/dotnet/arcade/issues/13025] -1. Adding testing infra to Staging pipeline is tracked (here)[https://github.com/dotnet/arcade/issues/13462] - - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/secret-sweep-and-clean-core-eng-13551.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/secret-sweep-and-clean-core-eng-13551.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/secret-sweep-and-clean-core-eng-13551.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/secret-sweep-and-clean-core-eng-13551.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,486 +0,0 @@ -# Dnceng Secret Sweep n' Clean  - -## Summary - -The .NET Engineering services team manages multiple resources that are -necessary for the development, servicing and release of the .NET -product. Several parts of the engineering and product infrastructure -depend on secrets that allow access to sensitive resources. - -The Engineering services team has already done extensive work to enforce -the security of these secrets: - -- Any resources that can be accessed by external contributors to the - .NET product don\'t have access to secrets (helix public queues, PR - builds in dnceng public instance) - -- All secrets are stored in a Key Vault, and rotated on a regular - cadence, partially without human interaction via the secret manager. - -- Secrets can be revoked at any moment in case of a suspected breach. - -- All secrets required by build and release pipelines are stored in - variable groups, with a backing key vault. - -- Access to Key vaults is (mostly) restricted to the Engineering - Services team. - -The company-wide increased focus on Security brings the need to do some -additional work in our secret management practices to ensure that we -follow several key principles. - -- **Least privilege**: Secrets should provide only the minimum - required level of access to sensitive resources. At the same time, a - secret should only allow access to a very limited set of resources. - -- **Zero trust**: Secrets can only be accessed by resources/people who - have a compelling need to access them. Not all secrets need to be - accessed by every member of the team. - -- **Minimizing shared information**: Resources and infrastructure - components should only access the secrets they need for their - functioning and no others. - -Keeping these principles in mind, there's a need to: - -1. Perform a point-in-time audit of our existing secrets and - secret-adjacent resources. - -2. Create new processes and guidelines on how to perform future audits - and cleanup. - -3. Develop improvements to our infrastructure in such a way that we can - uphold these principles by default when possible. - -## Stakeholders - -- .NET Engineering Services team (contact: \@dnceng) - -- All .NET Engineering Services partners - -## Risks - -**What are the unknowns?** - -- By changing the layout, scope and access level of our secrets, there - is a risk that we will break existing repo workflows or - infrastructure that we are not aware of. This will require providing - guidance and alternatives to teams that are using secrets in a way - that we didn't intend to. This is also an opportunity for - improvement and increased visibility into these hidden workflows. - -- There's a risk that restricting the access level of certain secrets, - some parts of our infrastructure will temporarily stop working while - we uncover discrepancies between what we think a secret does, and - how it's used. For example, we might uncover Personal Access Tokens - (PATs) that carry more access than their names imply. - -**Are there any POCs (proof of concepts) that need to be built for this -work?** - -Probably not. We will continue using and extending the existing secret -manager tooling. - -**What dependencies will this work have? Are the dependencies currently -in a state that the functionality in the work can consume them now, or -will they need to be updated?** - -This work shouldn't depend on any other work. The audit and cleanup can -happen at the same time as we develop new functionalities in the secret -manager. There will be a need to update various parts of our -infrastructure to account for any Key Vault layout changes and -permission guidelines that come as part of this work. - -**Will the new implementation of any existing functionality cause -breaking changes for existing consumers?** - -Yes. There is a good chance that restricting the access and scope of -certain secrets will break existing tooling and workflows that we are -not directly involved in, as well as places where we not using secrets -in the way we believe they are being used. As we both start enforcing -that every variable group needs to be sourced by a key vault, and we -restrict PAT scopes further, secret names will inevitably change, which -will cause the need for changes to YAML build definitions that rely on -the changed secrets. - -We will restrict variable groups to only be used by specific pipelines, -and this will most likely require reauthorization on the first run of -every pipeline that uses the variable group after the change. - -**Is there a goal to have this work completed by, and what is the risk -of not hitting that date? (e.g. missed OKRs, increased pain-points for -consumers, functionality is required for the next product release, et -cetera)** - -This work should be able to be completed in parallel with the product -development and the development of new features to our infrastructure, -and any impact should be brief. However, we should aim to complete this -work early in the .NET 7 development cycle to ensure that any new -infrastructure and product changes can take advantage of any new -guidelines and enhanced security that comes from this work. - -## Open Questions - -- Should we touch any product specific azure resources and key vaults? - -- What kind of access to resources will we need to give to different - ICs that are not part of the dnceng team? - -- Is attempting to manage Azure DevOps service connections in scope - for this epic? Managing these service connections has been a pain - point as the personal access tokens that back them expire without - any notification. - -## Components to change - -The components that we have identified that will need to be audited or -updated are: - -### Azure Key vaults - -We want to make changes to the layout and access policies of our key -vaults, and the individual secrets contained within them. The following -subscriptions are in scope for this work: - -- Dotnet Engineering Services - -- Dnceng-InternalTooling - -- HelixProd - -- HelixStaging - -**High-level activities** - -1. Split our existing vaults in such a way that we have a separation - between vaults that hold secrets needed to be accessed by - automation, from vaults that hold secrets that should be accessed by - humans. If there are any secrets identified as required by both - types of users (such as a limited set of SAS tokens for storage - account access), we should add them to the human accessible key - vaults. This way we can restrict access to the automation-only key - vaults. Some examples of each kind of secret are: - - 1. **Secrets required by automation only:** - - - Connection strings to databases - - - Azure storage account keys and SAS tokens - - - GitHub, Helix, Maestro and Azure DevOps Tokens belonging to bot - users - - - Proxy feed URLs for 2.1 servicing - - - Aka.ms secrets - - - Other secrets accessed by build and signing pipeline - - - GitHub Application client secrets - - 2. **Secrets required by humans** - - - Credentials to bot accounts (one time password seeds and - recovery codes, usernames, passwords) - - - SAS tokens for storage accounts that dnceng users don't have - access to, and for partner team access to dnceng storage - accounts (Helix, OSOB related). - -2. Split vaults that hold secrets that grant a high level of access to - resources, from vaults that hold secrets that grant more granular - access. Secrets like storage account keys and bot login information - allow for the creation of other secrets. We shouldn't keep bot - usernames and passwords in the same vaults that hold secrets - generated from those identities, and we shouldn't hold Storage - account keys and SAS tokens in the same vault. This grants us the - ability to restrict user access to the more granular resources and - tighten the access policies for secrets that grant a high level of - permissions. Only dnceng administrators should have access to the - broad access secrets. - -3. Make sure each one of our services has access to the least number of - vaults possible. This is something we already attempt with service - fabric clusters only having access to a single vault per - environment, but we should ensure this is the case for all our - infrastructure. - -4. Eliminate duplication of secrets where possible. There are bound to - be scenarios where duplication is necessary, but we should opt for - generating multiple secrets that do the same thing (such as PATs - with duplicate scopes, SAS tokens to storage accounts), so that they - can be rotated or revoked individually without affecting multiple - resources or separate components of the infrastructure. - -### Personal Access tokens - -We rely on various PATs to perform operations on behalf of our bot users -in several parts of our infrastructure across multiple GitHub and azure -DevOps organizations. Managing these PATs has historically proven -painful, and the impact of mismanaging them is very high, as usually our -bots have very high levels of access to most of our infrastructure. - -**High-level activities** - -1. Make sure that PATs only have only the minimum required set of - scopes for the functionality they perform. - -2. Create a sustainable process to generate Azure DevOps PATs for - multiple organizations in such a way that 1ES automation - () - will not change them underneath us. - -3. Make it easier to generate PATs for our bots, such that we can - encourage the creation and management of multiple PATs with the same - scopes as opposed to reusing the same PAT for multiple purposes. - -4. Ensure that every PAT used by infrastructure is performing - operations on behalf of our bot users, and not individual team - members. - -5. Make sure that every PAT is accounted for in a secret manager - manifest so that it can be managed by automation. - -6. Enforce the naming convention of \ for - existing and future PATs. - -### Azure DevOps Variable Groups - -The way that we pipe secrets from our key vaults to be used by build and -release pipelines is through azure devops variable groups. As their name -implies, these components can group variables in a logical collection. -These groups, and the variables contained within variables can then be -referenced in yaml pipelines and the classic editor. - -As part of this effort, we will audit every variable group in the dnceng -organization, as well as every matching variable group that we own in -the devdiv azure devops instance. - -**High-level activities** - -1. Ensure all variables groups that hold secrets are linked to a key - vault. If the variable group is not owned by the engineering - services team, work with the variable group owners to find the best - location for a vault to store the secrets. This will mean that - variable groups that currently mix secret and non-secret variables - should be split into two groups. - -2. Change all variable groups so that they are not granted access to - every pipeline in the Azure DevOps organization where they live, and - instead individual pipelines should be granted access on a "as - needed" basis. - -3. Audit pipelines and yaml templates to make sure they only request - access to the minimal set of variable groups they need to work - properly. - -4. Write tooling to enforce the new policies. We will need tools / - scripts that: - - a. Identify variable groups that are not linked to vaults - - b. Identify secrets that are present in variable groups but are not - used by any pipeline - -### Azure Storage Accounts - -The .NET engineering infrastructure relies on several storage Azure -storage accounts so that our services and pipelines run properly. As -part of this effort, we should make sure that broad access to storage -accounts is only granted to administrators, while we improve the tooling -needed to minimize the effects of cycling storage account keys. - -For the purposes of this work, we will make a distinction between -infrastructure development and product release storage accounts. - -**Product release storage accounts** refer to the storage accounts -where we upload bits that we will end up releasing to the public, -servicing releases, as well as the source-build tarballs that we share -with RedHat. Dnceng has limited access to these accounts, as they are -hosted in subscriptions outside of general access. We want to focus most -of our effort in this group of storage accounts. - -The storage accounts that fall in this group are: - -- Dotnetcli - -- DotnetcliMSRC - -- DotnetCliChecksums - -- DotnetCliChecksumsMSRC - -- Dotnetbuilds - -- DotnetFeed - -- DotnetFeedMRC - -**Infrastructure storage accounts** refer to storage accounts that are -needed for the functioning of services and infrastructure owned by -dnceng, for example the Helix service and autoscaler, and the -Helix-machines repository. - -**High-level activities** - -1. Audit our infrastructure for places where we use storage account - keys for access and try to replace them with SAS tokens instead. - Update all places where it's reasonable to make such changes. - -2. Audit existing SAS tokens in our key vaults to make sure that they - only have the least required access to the resources they grant - access to, and to make sure the secret names match the access level - the sas tokens grant. - -3. As specified in the Azure Key Vault section, split the vaults where - we store storage account keys from the vaults where we store SAS - tokens. No individuals in dnceng or the product teams should have - access to the vault where we store the keys. In cases where these - secrets need to be cycled, access to these vaults should be granted - either JIT, or there should be breadcrumbs so that devs know which - users have access to the resources. - -4. Cycle all keys for product release storage accounts once they are in - their own vault. - -5. Create SAS tokens managed by the secret manager tooling to grant - restricted access to individual containers in these storage - accounts. - -6. Write guidance for access policies to the product release storage - accounts. Create additional storage accounts for non-release - workflows that have historically used other storage accounts without - a compelling reason to do so. - -### Secret Manager - -Secret manager is the tooling we built to be able to manage secrets in -an automated fashion. Part of the work in this effort will require -additions to secret manager to achieve the level of enforcement and -modularity that we want to add to our key vaults and secrets. - -**High-level activities** - -1. Improve the usability and documentation around secret manager, with - usage examples and different examples of commands that should run - for different scenarios. As well as detailed documentation on how to - onboard each type of supported secret. - -2. Add the understanding of "dependent secrets" to secret manager. A - dependent secret is a secret that should be regenerated once another - secret is cycled or revoked. An example of this scenario is that all - SAS tokens that belong to a storage account need to be regenerated - once both keys to a storage account are cycled. - -3. Add the ability to automatically generate PATs through the tool - based on metadata in the secret manifests that shows which user, - which organizations and scopes PATs should be generated with. - -4. Add the ability to enforce naming conventions in secrets in the - manifests. We can make secret manager fail pipelines if it detects a - secret with an incorrect naming convention. - -6. We need to make a pass through all the existing secret manifests to - make sure there is enough information in each one of them for secret - types that the tool can't rotate automatically, such as aka.ms - secrets. - -## Serviceability - -#### How will the components that make up this epic be tested? - -For the required changes to secret manager, we will use and extend the -existing tests to make sure that any new scenarios don't break existing -functionality. - -For the secret audit and restructuring of vaults, it is difficult to -test whether a change to a secret will have a negative impact on where -it's used, so we will rely on a deployment strategy that minimizes the -impact of any scope or permission changes we perform. For secrets that -are used inside our different services and infrastructure, we will -depend on the test coverage of the different services to make sure that -changes to secrets do not cause breaking changes to the services. - -#### How will we have confidence in the deployments/shipping of the components of this work? - -Secrets used by our services will be tested as part of the service -deployments themselves. Changes to secrets used by pipelines will be -tested by running the pipelines that use them. - -## Rollout and Deployment - -### How will we roll this out safely into production? - -Changes to secret manager will rely on running the new functionality -against the staging versions of the manifests for the different -services. There is usually some discrepancy between the staging and -production secrets but making sure the staging services still work with -updated manifests is a good start. - -### How often and with what means will we deploy this? - -Changes to secret manager will be deployed weekly as part of the -arcade-services deployments. Changes to the layout of vaults and secrets -themselves will need to be deployed in stages: - -**For new vaults/secrets created as part of this epic**: - -1. Create copies of the existing secrets in their new locations - -2. Inform partners that we will be working on a particular set of - secrets, and to expect some disruption while we make the switch - -3. Change references to the secrets to pull from their new locations - -4. Ensure the secrets work when pulled from the new locations - -5. Delete the old secrets / vaults. - -**For existing secrets that lose some privilege** - -1. Inform partners that changes to secrets are going to be performed, - and to let us know if they see any of their workflows affected in a - negative way - -2. Monitor the pipelines and services that use the secrets that got - rescoped to make sure they are still functioning. - -### What needs to be deployed and where? - -Code changes to secret manager will be deployed via arcade-services, -where the tool lives. - -New key vaults will be deployed to the dnceng owned subscriptions, -depending on where the services that use the vaults live. - -### What are the risks when deploying? - -There are multiple risks when trying to make changes to secrets: - -- Restricting access to storage accounts may break some teams' - workflows. We should work with affected teams to provide guidance - for how to securely access the resources they were previously used - to accessing without restriction. - -- Changing the scopes of PATs has the risk of breaking infrastructure - that assumed the PATs had undocumented scopes. We should regenerate - any PATs and secrets with the proper scopes when this happens. - -- Changes to pipeline access for variable groups will need to be - accompanied by manual authorization of every pipeline that uses the - variable group. We should write instructions for the entire dnceng - team, so they know how to evaluate and perform this authorization. - -## FR Handoff - -### What documentation/information needs to be provided to FR so the team is successful in maintaining these changes? - -Part of the epic work involves better documentation and guides for the -usage of secret manager. We will also write instructions for any -potentially disruptive changes to secret access and how to deal with -them. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Csecret-sweep-and-clean-core-eng-13551.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Csecret-sweep-and-clean-core-eng-13551.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Csecret-sweep-and-clean-core-eng-13551.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/stateless-pools-cost-analysis.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/stateless-pools-cost-analysis.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/stateless-pools-cost-analysis.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/stateless-pools-cost-analysis.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,205 +0,0 @@ -# Stateless Pools - A Cost Analysis - -## Purpose -In order to determine the costs of switching from stateful pools to stateless ones, the following study was conducted. -Switching to stateless pools will resolve bug [dotnet/core-eng#14683](https://github.com/dotnet/core-eng/issues/14683), which -involves machines running out of disk space due to multiple runs on the same machine. 1ES has informed us that the working -directories on stateful machines will not be cleaned between runs and that we should switch to stateless pools in order to -avoid this problem. - -# Time Cost - -## Methodology -Three pipelines were examined as part of this study: runtime (ID 686), runtime-dev-innerloop (ID 700), and runtime-staging (ID 924). -These particular pipelines were picked because they could all be triggered via a single runtime PR. -To determine a baseline for the stateful 1ES pools, runs between 8 Oct 2021 and 22 Oct 2021 in the TimelineBuilds table in Kusto were queried. -Specifically, this this study only concerned itself with runs which met the following criteria: -* _Successful_: in order to make sure builds were as similar as possible, all failing runs were excluded from the results -* _Main_: once again for the purpose of consistency, only runs targeting the `main` branch were used -* _Inlier_: builds which took longer than five hours to complete were excluded from the dataset in interest of eliminating outliers - -To acquire the data for the stateless pool, 10 runs of these pipelines were triggered manually targeting the -NetCore-Public-Int pool, which was manually set to be stateless and capable of scaling out to 300 machines. -The sample size for these runs is necessarily much smaller than the baseline due to time constraints. The runs were -triggered on 22 Oct 2021 and 25 Oct 2021. - -BuildIds of stateless runs are as follows: -* runtime: `(1436605, 1436874, 1437134, 1439516, 1439517, 1439738, 1439739, 1439773, 1439774, 1439775)` -* runtime-dev-innerloop: `(1436606, 1436862, 1437004, 1437087, 1439512, 1439618, 1439703, 1439745, 1439776, 1439777)` -* runtime-staging: `(1436608, 1436875, 1437086, 1439514, 1439545, 1439779, 1439781, 1439782, 1439783, 1439784)` - -### Caveats and Limitations - -A few notable differences exist between our two samples: -* The sample size differs massively. A large sample size can be obtained for the baseline and it was, but the smaller sample -size for the delta inherently makes conclusions from that data less reliable. -* The baseline data, while having a large sample size, contains runs over many different commits. While they are likely -still similar enough to be useful for analysis, this differs from the delta data which contains runs over only a single -commit. -* While many different jobs were being run all at once during the delta data, this still does not reflect realistic usage -in prod. -* Not every run in the delta data was 100% successful -- while none presented a catastrophic failure, several had minor -failures due to test flakiness. -* This study still only reflects the usage of one (albeit very large) repo. - -### Queries Used -To determine average & standard deviation: -``` -TimelineBuilds -| where DefinitionId == [def] and QueueTime > datetime(10/8/2021) and QueueTime < datetime(10/22/2021) and Result == "succeeded" and TargetBranch == "main" -| extend BuildTime = StartTime - QueueTime -| where BuildTime < 5h -| distinct * -| summarize avg(BuildTime), totimespan(stdev(BuildTime)) -``` - -To obtain count data bucketed in fifteen minute intervals: -``` -TimelineBuilds -| where DefinitionId == [def] and QueueTime > datetime(10/8/2021) and QueueTime < datetime(10/22/2021) and Result == "succeeded" and TargetBranch == "main" -| extend BuildTime = StartTime - QueueTime -| where BuildTime < 5h -| distinct * -| summarize count() by bin(BuildTime, 15m) -| order by BuildTime asc -``` - -To obtain percentile data: -``` -TimelineBuilds -| where DefinitionId == [def] and QueueTime > datetime(10/8/2021) and QueueTime < datetime(10/22/2021) and Result == "succeeded" and TargetBranch == "main" -| extend BuildTime = StartTime - QueueTime -| where BuildTime < 5h -| distinct * -| summarize count() by bin(BuildTime, 15m) -| summarize percentilesw(BuildTime, count_, 50, 75, 95) -``` - -To obtain job start time: -``` -TimelineBuilds -| where DefinitionId == [def] and QueueTime > datetime(10/8/2021) and QueueTime < datetime(10/22/2021) and Result == "succeeded" and TargetBranch == "main" -| extend BuildTime = FinishTime - QueueTime -| where BuildTime < 5h -| distinct * -| summarize arg_max(FinishTime, *) by BuildId -| join kind=inner (TimelineRecords - | where Order != 0 - | where strlen( Path ) == 11 - | where WorkerName has "NetCore1ESPool" - | summarize arg_max(FinishTime, *) by RecordId, BuildId ) on BuildId -| extend JobStartTime = StartTime1 - QueueTime -| summarize count(), min(JobStartTime), avg(JobStartTime), totimespan(stdev(JobStartTime)) -``` - -To obtain job percentile data: -``` -TimelineBuilds -| where DefinitionId == [def] and QueueTime > datetime(10/8/2021) and QueueTime < datetime(10/22/2021) and TargetBranch == "main" -| extend BuildTime = FinishTime - QueueTime -| where BuildTime < 5h -| summarize arg_max(FinishTime, *) by BuildId -| join kind=inner (TimelineRecords - | where Order != 0 - | where strlen( Path ) == 11 - | where WorkerName has "NetCore1ESPool" - | summarize arg_max(FinishTime, *) by RecordId, BuildId ) on BuildId -| distinct * -| extend JobStartTime = StartTime1 - QueueTime -| summarize count() by bin(JobStartTime, 1m) -| summarize percentilesw(JobStartTime, count_, 50, 75, 95) -``` - -## Results -The percentiles indicate the percentage of builds that finish in less than the given time (bucketed in 15 minute increments for build times -and one minute increments for job start times). - -### runtime - -#### Build Times -| **Metric** | **Baseline** | **Delta** | -|-------------|--------------|-----------| -| Sample Size | 154 | 10 | -| Mean | 1:52:48 | 2:42:32 | -| StDev | 0:40:56 | 0:32:48 | -| 50th %ile | 1:45:00 | 2:45:00 | -| 75th %ile | 2:15:00 | 3:15:00 | -| 95th %ile | 3:30:00 | 3:30:00 | - -#### Job Start Times -| **Metric** | **Baseline** | **Delta** | -|-------------|--------------|-----------| -| Sample Size | 11186 | 1122 | -| Minimum | 0:02:17 | 0:03:33 | -| Mean | 0:18:53 | 0:29:03 | -| StDev | 0:15:12 | 0:20:01 | -| 50th %ile | 0:14:00 | 0:17:00 | -| 75th %ile | 0:31:00 | 0:50:00 | -| 95th %ile | 0:41:00 | 1:00:00 | - -### runtime-dev-innerloop - -#### Build Times -| **Metric** | **Baseline** | **Delta** | -|-------------|--------------|-----------| -| Sample Size | 552 | 10 | -| Mean | 0:57:58 | 1:02:09 | -| StDev | 0:09:11 | 0:04:32 | -| 50th %ile | 1:00:00 | 1:15:00 | -| 75th %ile | 1:15:00 | 1:15:00 | -| 95th %ile | 1:15:00 | 1:15:00 | - -#### Job Start Times -| **Metric** | **Baseline** | **Delta** | -|-------------|--------------|-----------| -| Sample Size | 3865 | 70 | -| Minimum | 0:00:19 | 0:03:15 | -| Mean | 0:03:56 | 0:11:33 | -| StDev | 0:04:14 | 0:03:39 | -| 50th %ile | 0:03:00 | 0:12:00 | -| 75th %ile | 0:06:00 | 0:14:00 | -| 95th %ile | 0:11:00 | 0:18:00 | - -### runtime-staging - -#### Build Times -| **Metric** | **Baseline** | **Delta** | -|-------------|--------------|-----------| -| Sample Size | 196 | 10 | -| Mean | 1:17:38 | 3:15:24 | -| StDev | 0:49:57 | 0:51:29 | -| 50th %ile | 1:00:00 | 3:30:00 | -| 75th %ile | 2:15:00 | 4:15:00 | -| 95th %ile | 2:45:00 | 4:15:00 | - -Unfortunately, runtime-staging might not be the most useful pipeline for drawing conclusions as the build time -distribution appears bimodal which means the mean build time is not as helpful a measurement. - -#### Job Start Times -| **Metric** | **Baseline** | **Delta** | -|-------------|--------------|-----------| -| Sample Size | 746 | 80 | -| Minimum | 0:01:46 | 0:03:44 | -| Mean | 0:12:48 | 0:20:43 | -| StDev | 0:04:19 | 0:10:23 | -| 50th %ile | 0:13:00 | 0:13:00 | -| 75th %ile | 0:16:00 | 0:16:00 | -| 95th %ile | 0:20:00 | 0:19:00 | - -## Conclusions - -While it is difficult to fully attribute one hundred percent of the build time increases to the stateless pools (especially with the delta -data having a larger standard deviation in the runtime job start data and both the runtime-staging data sets), there is still a marked -increase in both the build time and the job queue time for all observed pipelines. - -Of particular note is the runtime-dev-innerloop pipeline, which has very tight data sets for both the baseline and the delta. With lower -standard deviations for both builds and jobs, we saw mean build time increase by five minutes and mean job start time increase by -**nearly eight minutes**. In worst-case scenario runs, such an increase would translate to very high increases in overall build time, -which may be what is reflected in the runtime and runtime-staging mean build time increases. - -Given this data, it is the opinion of the author that stateless pools do not represent a reasonable alternative to stateful pools with -workspace cleanup. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cstateless-pools-cost-analysis.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cstateless-pools-cost-analysis.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cstateless-pools-cost-analysis.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/telemetry-monitoring-for-android-and-apple-platforms-13607.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/telemetry-monitoring-for-android-and-apple-platforms-13607.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/telemetry-monitoring-for-android-and-apple-platforms-13607.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/telemetry-monitoring-for-android-and-apple-platforms-13607.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,303 +0,0 @@ -# Monitoring for Android and Apple platforms - -As part of [#10420](https://github.com/dotnet/core-eng/issues/10420), we started supporting Android and Apple mobile platforms in Helix. -These new on-prem devices are different in nature to the hardware we were managing so far. -The devices (and simulators) can "break" in ways we are unable to detect at the moment and thus new approach to monitoring these devices is needed - -**The goal of this design document** is to capture our plan for -- why, how, when and what telemetry to collect, -- what alerting to create based on this telemetry. - -## Motivation - -The main goal is to learn fast about mobile devices in some failed state such as inable to install/run an application. -Currently, we are unable to achieve this because we don't have enough data around the singular operations (XHarness commands) we perform with the devices. -Furthermore, we don't have data about the big picture. - -To mark a mobile device as faulty, we need more than a single failing operation as it is hard to decide based on one data point. -The reason is that for some groups of problems we need a margin for cases where an operation fails but it's a user error that we just couldn't distinguish from an infra issue. -For example, if we spot a single installation failure the cause can be an app that was badly built and fails to install which is an user error. -However, if we see a high percentage of failures over time, probably the device is the faulty part (memory is full, emulator is hanging..). -In the large amount of data we will have (tens of thousands of operations per day), we will be able to account for these and alert more reliably. -This is the reason we we need to collect and store data about a series of operations and alert based on the whole. - -## Stakeholders - -- **.NET Engineering Services** - team servicing the Helix devices -- **DDFUN** - team managing the hardware - in future, they might be involved in the alert response process - -## Scope - -The mobile platforms that are in scope of this work are: -- iOS devices (iPhones) -- AppleTV devices -- Android devices -- Ubuntu Android queues with Android emulators -- OSX queues with Xcode and Simulators - -The overview of the queues and Mobile devices in Helix can be found [here (Mobile devices for .NET testing)](https://dev.azure.com/dnceng/internal/_wiki/wikis/DNCEng%20Services%20Wiki/107/Mobile-devices-for-.NET-testing). - -## Alerts - -The main goal is to learn about device failures such as inability find the device or to install/run an application there. -This will be achieved by creating Grafana alerts. -There are several alerts we want to fire that we know about already. -These events are usually signaled by XHarness CLI exiting with a specific return code. -Most of these events are already captured in the [wrapper scripts in Helix SDK](https://github.com/dotnet/arcade/blob/e6abc186425e675e0f201acc1c33e183a0e51c86/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/xharness-runner.apple.sh#L154-L181) and currently we only perform work item retry and reboot the machine, hoping the work item will be dealt with on a healthy machine. -The problem with this approach is that broken machines stay broken silently. -The monitoring will help us learn about these cases. - -Examples of these event are: -- HW device is not found on a queue with HW devices – this usually means the device is stuck after a reboot or similar. -- Apple Simulator is not found while we are not requesting a specific runtime version (we target `ios-simulator-64` for example, which should always be available, whereas `ios-simulator-64_14.2` might not). -- Android emulator is not available on a queue where we expect it to be running. -- Long taking operations - we see this for iOS simulators which leak memory/CPU which manifests as the installation of applications slowing down. -- Low SD card / internal storage disk space - root cause of installation failures in Android. - -### Implementation plan for alerts - -**Stage 1** -At first, we will focus on detecting fatal errors when they happen such as "device is not visible". -These are usually easy to detect since we will get almost a 100% failure rate once a device goes into this state. -We can detect this over a short period of time and alert. -The customer won’t be impacted as much thanks to the retry/reboot we already do so this is transparently dealt with. -It is important for us to know as these things now happen silently and we never learn about them. -We will be able to take the device offline and investigate it. - -**Stage 2** -As we gain more experience and investigate devices in broken states, we will learn to collect new metrics that can be used to predict breaks before they happen and avoid customer impact. -Examples can be: -- Long taking operations - we see this for iOS simulators which leak memory/CPU which manifests as the installation of applications slowing down. -- Low SD card / internal storage disk space - root cause of installation failures in Android. - -**Stage 3** -For some events, we already self-heal by doing a retry/reboot and thus we might not need an alert. -We can, however, be notified about this, so that we know when and how often these things happen. -This stage can introduce additional events as described above. -These are rather "nice to have" so we can consider the business need later and potentially omit this stage. - -## Challenges - -Every mobile device is connected via a USB cable to a host machine which acts as a Helix agent - Helix client runs on the host machine only, never the device. -In contrary to the other platforms, the host machines are not interesting to us very much, we care about the devices. -Since the Helix client always runs on host machines and the devices are not visible from the Helix perspective, the heartbeat mechanism won't detect bad states of these devices. - -Furthermore, it is quite hard to determine the health of these devices for several reasons: -- We need specialized tooling to find the devices (XHarness, ADB, mlaunch). -- This tooling needs to be updated frequently (to react to new versions of iOS, Android...). -- The operations that gather device information can take even longer time than the over all runtime of a large portion of work items we run today that finish in several seconds (e.g., iOS Simulator BCL tests). -- It is difficult to tell a bad state as some of the problems do not manifest until we try to perform certain actions with the device, e.g., a device is locked but installation succeeds, only trying to run the app will fail. - -We explored several approaches but they had several drawbacks which would make them hard to implement/maintain: - -- Adding a monitoring command to the XHarness CLI and calling it at the beginning and at the end of the job. - - It would add sizable time to all the work items. - - We don't know which device/emulator will be used until we read the application metadata and only after then we look for the appropriate device. - - We can only detect some problems when we try to install/run applications on the devices which wouldn't work for this command neither. We care about results of some of the operations more than the state of the device because we just don't know the state is corrupted until some operations fail. - - The user can call many XHarness commands as part of a single Helix work item - they can install the app once and then run it several times over with different parameters. We wouldn't know when the state got corrupted. - -- Adding a monitoring module to the machines that would scan the environment periodically and report back. - - Shares some of the drawbacks from above. - - Would be hard to maintain/update together with all of the dependencies. - - Might interfere with currently running Helix work item. - -## Architecture & design - -> **TLDR:** XHarness will collect diagnostics data during execution and Helix SDK will send them to an Application Insights account as custom metrics so that Grafana can alert based on it. - -### Affected components: -- **XHarness CLI** - tool used to run the Android/Apple tests ([`dotnet/xharness`](https://github.com/dotnet/xharness)) -- **Helix SDK** - MSBuild targets that enable creation of XHarness Helix workloads ([`dotnet/arcade`](https://github.com/dotnet/arcade/blob/main/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/Readme.md)) -- **XHarness workload scripts** - wrapper scripts that execute the commands supplied by the user ([`dotnet/arcade`](https://github.com/dotnet/arcade/blob/main/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/)) -- **Grafana dashboards** - definitions of monitoring charts and alerts ([`dotnet/arcade-services`](https://github.com/dotnet/arcade-services/tree/main/src/Monitoring)) - -### Data collection process - -Based on the requirements and the challenges we face we can collect the data in the following way: - -1. We will add a diagnostics mode for every command of the XHarness CLI. There will be a new option and XHarness will also accept this an environment variable. -2. As the command runs in this mode, it will collect diagnostics data (i.e., create a diagnostic file) with information about which operation was running and how it ended. It will note down which device was used and other useful statistics. -3. Since we control the execution of user's commands via the Helix SDK wrapper scripts, we can set the environmental variable at the beginning to execute always in the diagnostic mode. We can then collect the results at the end. -4. We will send the diagnostics to Application Insights (later to Kusto) at the end of the Helix work item. We only need to make sure we have enough time (few seconds at most) in the Helix work item to send the data after user's commands are finished. However, since we control the execution of those, we can time-constrain them and leave a buffer for ourselves. - -### Data storage - -To store the data, we will use an already existing Application Insights account [helix-client](https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/68672ab8-de0c-40f1-8d1b-ffb20bd62c0f/resourceGroups/helixinfrarg/providers/microsoft.insights/components/helix-client-prod/overview) and upload the diagnostics data there as a [custom metric](https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-custom-overview). -To send the data, we will need the API key and then send a request to Azure. -We have code and infrastructure for this in place already [in the Helix client (appinsights.py)](https://dev.azure.com/dnceng/internal/_git/dotnet-helix-machines?path=%2Fresources%2Fhelix-scripts%2Fhelix%2Fappinsights.py). -No SDL / threat model changes would be necessary for this. - -The metric will have a static name common for all events generated for these purposes such as `XHarnessOperation`. -The value will be the exit code of the operation and the rest of the properties will go to **customDimensions** (an assorted dictionary). -We can then query the data points like in the following example: - -```sql -customMetric -| where name == "XHarnessOperation" and value != 0 -| where customDimensions.platform == "android" -| where customDimensions.command == "install" -``` - -In a later stage, we will migrate to Kusto and send the telemetry together with Helix job events that we are sending already. -Currently, we are storing the events in the `Metrics` table which also has its own `CustomDimensions` column. -We will decide at the time of implementation whether we will create our own table or re-use this existing one, it is an implementation detail. -The queries for AI and Kusto are very similar, using the same language so it will be quite effortless to migrate the charts and alerts. - -### Moving to Kusto - -In the design stage, we have come to a conclusion that a follow-up effort will be to generate some more rigorous reports about the mobile devices and their reliability. -For this, Kusto is more convenient place to store the data because of reliability and linkability with other job data. -The reports are a new requirement that wasn't originally part of the epic and since we need to still support .NET 6, we are on an aggressive schedule. -Because almost no additional work is introduced when switching to Kusto and also for reasons stated below, we we will use Application Insights to store the data at first and then move to Kusto in a second iteration: - -- Kusto changes are more complicated as the data travels to Kusto through EventHub and an SQL table (handled by the Helix service). This means they require Helix service rollout and then Helix machines rollout (for Helix client) so it will take time to get them out. -- Application Insights only require a PR in Arcade and thus are a better tool for iterating in case we don't get everything right the first time. -- With AI, we can have the data available immediately and this iterate faster with graphs/alerts to see if we missed something, no release cycle needed. -- We can have data while we work on changes needed for Kusto already and service the platforms to better accomodate the .NET 6 schedule. -- No extra work needs to be done when choosing AI, everything is in place already. -- No additional work is introduced when switching to Kusto, only Kusto specific work that needs to be done anyway, we will just use different Python class to send the same data to a different place (from Helix perspective). - -### Collected data - -It is important that XHarness CLI stays Helix agnostic as it is used by other teams in scenarios not related to Helix at all. -This means that XHarness CLI will only collect data related to the operations it performs on the device. -We will then enrich the diagnostics XHarness data with Helix environment specifics that will help us identify problematic machines. - -**XHarness CLI data:** -- Platform (android/apple) -- Executed command (install/test/run...) -- Exit code -- Duration of the command -- Emulator/Simulator ID (can also be architecture + OS/API version string) - -As we continue to identify new properties of the devices that can help prevent/detect problems, we can extend these data points or add new metrics. -We can also only choose to report some in case of unsuccessful operations only. - -**Helix SDK data:** -- Machine name -- Queue name - -There are other data points we might choose to collect (XHarness CLI version, Helix work item friendly name…) but we are constrained by some limits set for custom metrics. - -**Example data** - -Example data for 1 run that uses the granual XHarness operations (we also have `test` command that performs all `install`, `just-test` and `uninstall`) can look something like this: - -| Metric | Value | Cloud role | Cloud instance | CD.platform | CD.command | CD.target | -|---------------------------|------------------|----------------------------|----------------|-------------|------------|-----------------| -| XHarnessOperation | 0 (SUCCESS) | osx.1015.amd64.iphone.open | DNCENGMAC049 | apple | install | ios-device 14.4 | -| XHarnessOperationDuration | 40 (sec) | osx.1015.amd64.iphone.open | DNCENGMAC049 | apple | install | ios-device 14.4 | -| XHarnessOperation | 1 (TESTS FAILED) | osx.1015.amd64.iphone.open | DNCENGMAC049 | apple | just-test | ios-device 14.4 | -| XHarnessOperationDuration | 150 (sec) | osx.1015.amd64.iphone.open | DNCENGMAC049 | apple | just-test | ios-device 14.4 | -| XHarnessOperation | 0 (SUCCESS) | osx.1015.amd64.iphone.open | DNCENGMAC049 | apple | uninstall | ios-device 14.4 | -| XHarnessOperationDuration | 4 (sec) | osx.1015.amd64.iphone.open | DNCENGMAC049 | apple | uninstall | ios-device 14.4 | - -*\* CD means Custom Dimensions, just the object is expanded into separate columns* - -The `Value` column for `XHarnessOperation` is the exit code of XHarness and number of seconds for `XHarnessOperationDuration`. -From these data points, we will be able to gather the success rate of each operation per every machine. -We can be strict for alerting based on some exit codes and more benevolent in other: -- `DEVICE_NOT_FOUND` results on machines in a queue with devices (not emulators) is probably an error we want to know about fast as it means the device is turned off. -- `INSTALLATION_FAILED` for Android devices can be the applicaiton's fault (bad app) but can also mean low storage space, so a we can have lower expectations but still want to be alerted about some levels of dropped SLA on a device. - -### Metric limits and quotas - -There are some [limits set for Application Insights custom metrics](https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-custom-overview#quotas-and-limits): - -- Maximum of 10 custom dimensions per metric -- Limit of 50,000 total variations of values stored in the custom dimensions - -The second limit means that we need to consider the cardinality of each custom dimension and multiply them. This number must not be larger than 50,000. -The cardinalities of proposed dimensions are: - -| Dimension | Values | # of values | -|------------------|----------------------------------------------------------|:-----------:| -| Platform | android/apple | 2 | -| Executed command | test, run, just-test, just-run, install, uninstall | 6 | -| Test target | Android – 10 API versions, 32/64 bit – roughly 20 values | | -| | Apple – iOS/tvOS, OS version – roughly 30 values | 50 | - -We don’t need any other dimensions because: -- Exit code will be the value of the metric -- Machine name and queue name are stored in the metric’s `cloud_RoleName` and `cloud_RoleInstance` fields -- Simulator vs device can be deduced from queue name -- Device ID can be deduced from machine name - -Altogether, we’re looking at 600 variations which gives us space to grow still. -There are also other fields in the metrics we could probably use such as `client_OS`. -Nonetheless, if we choose to collect stats such as free RAM, we should consider adding it as a new metric and keeping its custom dimensions same. - -### Data volume & estimated costs - -The [metrics pricing](https://azure.microsoft.com/en-us/pricing/details/monitor/) doesn't include custom dimensions yet since they are in preview still. -The metrics themselves cost $0.258/MB with first 150 MB per month free. -Each metric is considered 8B so we have 18.75 million metric data points for free. - -Currently, the AI account receives 1,500,000 data points per day which amounts to 2-3 GB with 100 GB being the daily volume limit. -The current monthly cost of the account is 170 USD. - -The new metrics we want to collect will be generated by around 70,000 XHarness operations per day which amounts to 4-5 USD per month based on these calculations (70000 data points * 30 days * 8 bytes / 0.258 USD per MB), possibly less if we hit the higher volume band which has lower rates. - -## Feature rollout plan - -This feature will be on-demand and users of Helix SDK will be able to turn it for their Helix jobs on by setting an MSBuild property. -We can experiment this way by spawning single tests jobs from the local environment. -After the feature is code-complete: - -- We will start by turning the feature on in **dotnet/xharness** where we have few runs per day (lower tens of metric data points). -- We will continue by rolling it out gradually for the BCL tests in **dotnet/runtime**. We can do it test suite by test suite, queue by queue, platform by platform. The property can be conditioned in MSBuild easily as part of a **dotnet/runtime** pull request. In case of problems (we start hitting some limits), we can dial it back by another **dotnet/runtime** PR. -- We will finalize the rollout by adding it to the runtime tests in **dotnet/runtime** (which requires them to start using the Helix SDK properly – pending work on their part). -- We will make the feature be opt-out and on by default in the Helix SDK. - -To make a change to the system, we need to change XHarness / Helix SDK and let the dependency updates flow to **dotnet/runtime**. - -## Risk - -- What are the unknowns? - *We only estimated the volume and variety of the data stored in Application Insights based on already existing data. We might need to create a separate account in case we need more custom dimensions. We however have control over this because we can roll out platform by platform.* -- Are there any PoCs required to be built for this epic? - *No, this work will be easily testable before put into production.* -- What dependencies will this epic have? Are the dependencies currently in a state that the functionality in the epic can consume them now, or will they need to be updated? - *The dependencies (XHarness, Helix SDK, Grafana) are in place and will only be extended.* -- Will the new implementation of any existing functionality cause breaking changes for existing consumers? - *No changes, just new functionality which is internal to our team only so no consumer impact.* -- Is there a goal to have this work completed by, and what is the risk of not hitting that date? - *There is no hard deadline. Having this delivered will have impact on the smoothness of work on .NET 6 for the mono teams.* - -## Serviceability - -- How will the components that make up this epic be tested? - *We have unit tests and E2E tests for both places with code changes (XHarness CLI, Helix SDK). We can verify that the data is flowing to Grafana before merging each change as we have E2E tests for XHarness and we can query by machine. We are unfortunately unable to have some sort of E2E test that makes sure Helix client changes don't break the Helix SDK integration. We would need to have the newest Arcade and Helix SDK in Helix Machines. This seems very complicated to do and my be a bad ROI.* -- How will we have confidence in the deployments/shipping of the components of this epic? - *We can test everything in PRs and roll out gradually before merging.* -- Identifying secrets (e.g. PATs, certificates, et cetera) that will be used (new ones to be created; existing ones to be used). - *Application Insights API key is already in place, also used on public queues so considered not a secret* -- Does this change any existing SDL threat or data privacy models? - *No, we already send data from the Helix client to the same AI account* -- Steps for setting up repro/test/dev environments? - *XHarness CLI can be run locally as is. Helix SDK integration tests can be triggered manually from `dotnet/xharness` and `dotnet/arcade` by devs.* - *We will have a staging Application Insights account.* - -## Rollout and Deployment -- How will we roll this out safely into production? - *We will feature switch this via an MSBuild property in client repos (dotnet/xharness with low traffic to begin with).* -- How often and with what means we will deploy this? - *Rollout process for all artifacts is already in place.* -- What needs to be deployed and where? - *XHarness CLI diagnostics functionality and Helix SDK features will be delivered via Maestro updates.* - *Alerts and charts will be deployed to Grafana using the regular `arcade-services` rollout.* -- What are the risks when doing it? - *No risks as we will feature switch this inside of a PR.* -- What are the dependencies when rolling out? - *None* - -## FR Hand off - -Documentation and FR hand off are subsequent goals of the parent epic and will be handled separately: -- All new alerting should be actionable with links to documentation. -- Team will be educated on how to service the new platforms. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Ctelemetry-monitoring-for-android-and-apple-platforms-13607.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Ctelemetry-monitoring-for-android-and-apple-platforms-13607.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Ctelemetry-monitoring-for-android-and-apple-platforms-13607.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/validation-notifications-arcade7299.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/validation-notifications-arcade7299.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/validation-notifications-arcade7299.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/validation-notifications-arcade7299.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,84 +0,0 @@ -# Notifications For Validate-DotNet - -## Summary - -For the results of the post-build/nightly validation pipeline to be actionable, repo owners or their representatives need to be notified of failures when they occur. We propose to use [BuildMonitor](https://dev.azure.com/dnceng/internal/_wiki/wikis/DNCEng%20Services%20Wiki/185/BuildFailureManagement) to monitor the results of the validation pipeline, and notify repo owners of the failures in their runs based on the tags associated with each build by opening issues in the repositories and adding the repo's 'Area-Infrastructure' label. With these notifications, we will be able to evangelize Validate-DotNet as the supported post-build validation platform, moving away from running validation in official builds completely. For Stage-DotNet, we can also open issues in core-eng, that we can tag with the Release team stakeholders who would then pass the issue on to the product team responsible for any failures. - -## Stakeholders - -- .NET Core Engineering Services team (contact: @dnceng) -- .NET Core Release team (contact: @leecow) -- All product teams that currently use, or will in the future use, Validate-DotNet for their validation needs - -## Risk - -- What are the unknowns? - - The main unknown is how to determine who, for each repository, should be notified on each issue that BuildMonitor will open. We will start by simply adding the repos' infrastructure label to the issues that are created, and we can update that to include an assignee if it becomes desired. - -- Are there any POCs (proof of concepts) required to be built for this work? - - No, as this work will simply extend BuildMonitor to be able to differentiate builds based on tags, as well as open issues in repositories other than core-eng. - -- What dependencies will this epic have? Are the dependencies currently in a state that the functionality in the epic can consume them now, or will they need to be updated? - - This work depends on the existing BuildMonitor project. This project will need to be updated, but the work is minimal and known. - -- Will the new implementation of any existing functionality cause breaking changes for existing consumers? - - Build Monitor will have to be extended so that we can open issues in multiple repositories. Today, BuildMonitor takes a single Issues definition, when we will need to specify multiple. Additionally, we will need a way to map monitors to the repos that they will open issues in. We should be able to do this in a way that does not break current functionality, but it may be a breaking change. - -- Is there a goal to have this work completed by, and what is the risk of not hitting that date? (e.g. missed OKRs, increased pain-points for consumers, functionality is required for the next product release, et cetera) - - The goal is to have this work completed by June 2021. There is no risk associated with slipping the date. - -## Serviceability - -- How will the components that make up this epic be tested? - - - Existing BuildMonitor tests will be extended to include the components added by this work - -- Identifying secrets (e.g. PATs, certificates, et cetera) that will be used (new ones to be created; existing ones to be used). - - No new secrets will be needed by the change. - -- Does this change any existing SDL threat or data privacy models? (models can be found in [core-eng/SDL](https://github.com/dotnet/core-eng/SDL) folder) -- Does this require a new SDL threat or data privacy models? - - No; the only PII used will be GitHub aliases / labels and this will be augmenting existing functionality. - -### Rollout and Deployment - -This section left blank as this will be part of an arcade-services component. - -## FR Hand off - -- What documentation/information needs to be provided to FR so the team as a whole is successful in maintaining these changes? - - As the changes for this should be minimal, no additional documentation should be needed. - -## Description of the work - -### Components changed - -#### Component: BuildMonitor - -- Changes to [BuildMonitorOptions](https://github.com/dotnet/arcade-services/blob/main/src/DotNet.Status.Web/Options/BuildMonitorOptions.cs) - - Restructure BuildMonitorOptions, AzurePipelineOptions, and/or IssuesOptions so that we can open issues in multiple repos, not just core-eng. This means either having multiple monitors/issues or linking monitor definitions to issue definitions. -- Changes to [AzurePipelineOptions](https://github.com/dotnet/arcade-services/blob/main/src/DotNet.Status.Web/Options/BuildMonitorOptions.cs#L20) - - Add an "tags" field to BuildDescription. Tags on a pipeline allow you to mark the build with a piece of metadata that you can filter on in the AzDO UI, and is also exposed via the AzDO api for pipelines. We use this data so that product teams can filter the pipeline to only see their builds. -- [ProcessBuildNotificationsAsync()](https://github.com/dotnet/arcade-services/blob/main/src/DotNet.Status.Web/Controllers/AzurePipelinesController.cs#L143) - - If Tags are set in BuildDescription, compare to the tags for the given build, to determine if this is a build of interest - - If custom text available, append it to the body of the issue description - -#### Component: Repository access - -- Build monitor needs to be given access to create issues in each repository that will require notifications. It is currently only enabled in core-eng - -#### Component: Schedule-Validation-Pipeline - -- Update tags for builds to tag with the repo, the channel, and repo-channel. Right now, we only tag as repo-channel - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cvalidation-notifications-arcade7299.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cvalidation-notifications-arcade7299.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Cvalidation-notifications-arcade7299.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/zenhub-migration-core-eng-15084.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/zenhub-migration-core-eng-15084.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/One-Pagers/zenhub-migration-core-eng-15084.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/One-Pagers/zenhub-migration-core-eng-15084.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,141 +0,0 @@ -# Migration from ZenHub to GitHub Projects (beta) - -Going forward, we need to move all of our issue tracking out of ZenHub. The replacement we have chosen is GitHub Projects (beta). While Projects (beta) is not a one-to-one mapping of ZenHub and its concepts, it will fulfill our needs as an issue tracking management system. - -## Stakeholders - -* .NET Core Engineering Services team (contact: @dnceng) -* .NET Partners who use ZenHub - -## Risks - -* What are the unknowns? - * The unknowns of this epic center around the Projects (beta) board as a whole: can it support the number of issues that we have open? What sort of compromises will we have to make with the new structure? Not everything is directly mappable, so we need to keep this in mind. The GitHub roadmap is [here](https://github.com/github/roadmap/projects/1). -* Are there any POCs (proofs of concept) that need to be built for this work? - * We will want a POC for the migration from the ZenHub board to the Projects (beta) board that takes an issue and adds it to the board, and also that takes an epic issue and converts it into a Projects (beta) field on the board - * We also will likely want a POC board, though several other teams have their own boards that we can use as examples for how they are being used (see the [.NET Docker board](https://github.com/orgs/dotnet/projects/58/views/1) as an example that uses the board, list, triage and epic views). -* What dependencies will this work have? Are the dependencies currently in a state that the functionality in the work can consume them now, or will they need to be updated? - * GitHub REST API - * GitHub GraphQL API - There is no C# API for the Projects (beta) APIs (and the C# API that there is for GraphQL is also in beta), so we are stuck with HTTP requests until we have a C# api to work off of. This could mean changes that happen underneath us could lead to issues that we don't see until we notice that we are missing issues being added to the board. - * ZenHub API -* Will the new implementation of any existing functionality cause breaking changes for existing consumers? - * It should not, other than the fact that the ZenHub board will no longer be our source of truth. -* Is there a goal to have this work completed by, and what is the risk of not hitting that date? (e.g. missed OKRs, increased pain-points for consumers, functionality is required for the next product release, et cetera) - * We want to complete the initial migration by the end of February, when our ZenHub subscription runs out. - * If at all possible, we should also have the automatic adding of issues/epics to the board done by then as well, so the board doesn't need to be manually managed. - -## Open Questions - -* Single board vs board-per-epic? - * Both have pros and cons, but a single board with all of the issues will be required for stand up, and is the most directly comparable to what we have now. - -* Where should the board live? - * Because we may have issues in multiple repositories (as we do now with arcade and core-eng), we need to create the board at the organization level, rather than the repository level. That means to get to the board, we will need to go through https://github.com/orgs/dotnet/projects?type=beta. This will enable folks who are working on issues in non-dnceng repositories to add their issues to the dnceng board (for example, Android/Apple issues in dotnet/runtime). - -* What are all the use cases for the ZenHub board, and how will those translate to the new Projects (beta) board? - * Epic Reviews - * Reviewing all of the epics to determine business priorities - * Reviewing a single epic - * Stand up - * Reviewing all of the work currently being worked on - * Either by epic or by status - * FR stand up - * Reviewing the FR issues currently being worked on - * Reviewing the FR backlog - * Triage - * Reviewing all issues that have currently not been assigned to an epic - * Individual user view - * Reviewing all issues assigned to a particular person - - All of these scenarios will be manageable in Projects (beta). The only things that don't seem translatable are: - - * Convert this issue into an epic - * We will need to manually add the epic label to the issue, and then have our own app or webhook that monitors for this label and adds the Epic issue to the board as an epic - * Additionally, the issues won't have all of the extra epic stuff (table with all of the issues and their statuses) that ZenHub gives us - * Sort by assignee on board view - * Currently, you can group by status and sort by assignee on the table view in Projects (beta), but not in the board view - * This may cause us to rethink how we run stand-up. Do we even need to be going over issues on an individual basis? Should our stand ups be adjusted? - -* How will we translate the concept of an "epic" to Projects (beta)? - -We have used Epics in ZenHub as a way to track the concept of business priorities. While GitHub does not have the exact concepts of epics, they are currently implementing features in issues to track issues in other issues: essentially, what we do with ZenHub epics now. This works by adding a task list to each tracking issue containing each issue that is tracked by that issue/epic. After discussions with GitHub, this is the recommended path forward. Additionally, there is a coming feature that will allow us to display the "Tracked in" issue on the Projects (beta) board, which is comparable to what we have with the ZenHub board. The major challenge with this approach is adding issues to an epic/tracking issue. Today, the only way to do so is to update the markdown description of the tracking issue with either the link, org/repo#issueNumber, or issue number (if it's in the same repository) of the tracked issue. This cannot be done from the tracked issue. Issues can be tracked in multiple tracking issues, and GitHub, behind the scenes, creates a tree structure that can be queried for async triage (though that is still in beta as well). Whether or not this query capability will be in REST or only graphql is still a question. - -This will be a major change to how we work when it comes to epics. We will need to be cognizant of updating the epic issue when we create a new issue (or, you can just add a checkbox to the task list and use the "Create issue from task" button that will appear after saving). Unlike before, where we went from issue to epic, will need to start thinking in an epic-first way, where we start at the epic and end at the issue. - -The nice thing about this is that each issue will have a "tracked by" link under the title, which will allow us to go back to the epic issue easily. - -* How will issues be added to the board? - * Projects (beta) does not have the ability to automatically add issues to the board when they are created - * Users can manually add issues at creation time - * We will need to use a webhook that watches for issue creation and adds those issues to the board automatically if the user didn't do so - -## Components to change - -This work consists of three parts: the new Projects (beta) board, a command line tool to do the initial port of the ZenHub board to the Projects (beta) board, and a service that adds issues and epics to the board as they are created and/or updated. - -### The Projects (beta) board - -We need to create a new board and add all of the required columns and fields so that it maintains parity with the ZenHub board. As we do this, we may decide to add new columns and/or remove columns that are rarely used or no longer fit our needs. - -### Command line tool - -This tool will do the following: - -* For every issue in arcade, core-eng, and xliff-tasks - * Add it to the new Projects (beta) board -* For every epic issue - * Add a section to the issue description for a task list - * Use the ZenHub API to get the list of issues in the epic and add it to the task list markdown if it isn't already there - -If all goes well, we will only run this command line tool once to get the initial port done, and then we will be able to switch over to the new board and rely on the service and users correctly adding new issues to epics/the project using the new methodology. - -### Service - -The service will be a webhook that monitors new and updated issues. - -* New Issue: - * If the issue is not already on the board, add it to the board - -Note: GitHub has an issue open to automatically add issues in a repository to a project. When that feature comes out, the service can be decomissioned. - -### Async Triage tool - -As part of this work, we will be removing the ZenHub board, and therefore must update the async triage tool to monitor the new Projects (beta) board rather than using the ZenHub APIs. - -The triage tool will need to walk through all the issues that are still open in each of our repositories, and discover the tracked in information. We can do this using graphql, though the api for it is under a feature flag. However, we should be able to easily get the information for every issue and use linq to identify the issues with no tracking information. My understanding is that when this is released, there will also be a REST API for it that we might be able to switch to. - -## Serviceability - -* How will the components that make up this epic be tested? - * With unit tests - * Potentially with the staging environment to update a staging copy of the board -* How will we have confidence in the deployments/shipping of the components of this work? - * We will monitor the board to as issues are added to arcade to make sure that they are also added to the new board. - -## Rollout and Deployment - -* How will we roll this out safely into production? - * We will have unit tests for the service - * We may have a staging board that uses the service in staging to update it, so that we can catch when changes cause the process to fail -* How often and with what means will we deploy this? - * This will be rolled out alongside Helix, and will follow the normal helix rollout process - -## FR Handoff - -* What documentation/information needs to be provided to FR so the team as a whole is successful in maintaining these changes? - * We will provide documentation for adding and debugging GraphQL queries - * We will provide documentation on how to run the migration tool, though it should only need to be run once - * We will provide documentation on new procedures for creating new epics, and how the github webhook works to update the board so that new issues can be added to epics - * We will provide documentation on how new issues are automatically added to the board, and how to debug the service if they are not being added - -## Monitoring - -We will use AppInsights for monitoring of the service and add grafana alerts for when there are errors adding issues to the board. - -## Decomissioning the CLI tool - -Once the migration is complete, we will no longer need the cli tool. After we have successfully helped the rest of the org migrate off of ZenHub, either using the CLI tool or not, we will remove the CLI tool so as to not clutter up helix-services. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Czenhub-migration-core-eng-15084.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Czenhub-migration-core-eng-15084.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5COne-Pagers%5Czenhub-migration-core-eng-15084.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/README.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/README.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/README.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/README.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# Rollout Score Card - -Each rollout will be scored accordingly. The mechanics are similar to golf scoring, in which the higher the number, the worse the score. The goal is a score of 0. Each item has a threshold associated with it and a score associated for each infraction. The "threshold" is the allowable amount for each item, and any amount over the allowable will incur penalties. - -The score card allows us to count up infractions against our rollouts. A successful rollout should be completed in the time allowed (i.e. threshold), and should not require any sort of intervention, like a hotfix or rollback. Also, we should not receive any customer complaints based on the rollout. A rollout like that would give us a score of 0, which is our goal. We should be able to drill down into the score to see why the penalties were incurred and improve our rollout process (e.g. adding validation, testing, et cetera). - -1. Time taken to rollout (Every 15 minutes over [rounded up] is worth 1 point). This value is calculated based on the total time to build and deploy the service (explicitly: the sum of the build pipeline's elapsed time and the release pipeline's elapsed time). End-to-end tests run after the service has been deployed are not added to this value. (Reason for this is that the faster our builds and deployments are, the faster we can make changes and hotfixes, if necessary). - - A. OS Onboarding Threshold: 1 hour - - B. Helix Threshold: 30 minutes - - C. Arcade-Services Threshold: 1 hour - -2. Number of critical/blocking issues as a result of the rollout (1 point per issue). Threshold is 0. - -3. Service downtime (availability and reliability) as a result of the rollout (1 point for every minute of downtime). Threshold is 0. - -4. Number of hotfixes (5 points per hotfix). Threshold is 0. Also, the time it takes to roll this out is cumulatively added to the initial rollout time (see #1 above). - -5. Number of rollbacks (10 points per rollback). Threshold is 0. Also, the time it takes to roll this out is cumulatively added to the initial rollout time (see #1 above). - -6. Failure to rollout (50 points). This is a scenario in which we have made a commitment to our customers (e.g. posted it in release notes), and did not meet the window to rollout. This includes partial rollouts (i.e. rollouts in which some parts of the deployment failed and were not remedied). - -Example: A rollout of OS Onboarding took 6 hours (20 points), Helix took less than 30 minutes (0 points), and Arcade-Services took 1 hour (0 points). A critical issue occurred in Helix (1 point), which resulted in the need for a hotfix (5 points) and another rollout of Helix, that took less than 30 minutes (0 points). During that time, the component in Helix that was broken caused a service downtime of an hour (60 points). This score of this rollout would have been 86 points. - -## Dashboard - -A Power BI dashboard of all rollout scorecard metrics so far can be found [here](https://msit.powerbi.com/groups/de8c4cb8-b06d-4af8-8609-3182bb4bdc7c/reports/6d2bd5cd-f96f-40df-af3f-33fd4cf1d82d). - -## Pre-Requisites - -In order for this to work, we'll need the entire team to be on board with this process: - -* Every hotfix needs to be noted as a hotfix for us to measure those metrics. Same with rollbacks. -* Issues (critical or not) that are a result of a rollout need to be marked as such with labels. - -The full guidelines for these are noted in the [Policy Document](/Documentation/Policy/DeploymentPolicy.md). - -## Automation - -Rollout Scorecards are automatically created, submitted, and logged by the [Rollout Scorer](https://github.com/dotnet/arcade-services/tree/master/src/RolloutScorer/Readme.md). - -## July 24, 2019 Rollout (Example) - -The July 2019 rollout served as the basis for this scorecard. Its scorecard can be found [here](Scorecard_2019-07-24.md). - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CREADME.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CREADME.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CREADME.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Rollout_Scorer_Automation_Proposal.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Rollout_Scorer_Automation_Proposal.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Rollout_Scorer_Automation_Proposal.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Rollout_Scorer_Automation_Proposal.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,67 +0,0 @@ -# Rollout Scorer Automation Proposal - -The [Rollout Scorer](/MiscTools/RolloutScorer/Readme.md) as it exists now is very useful for speeding up the process -of scoring our rollouts. However, it could still be made more valuable by fully automating it as a service that requires -minimal human intervention. - -## Defining and automating the remaining metrics -Currently, the rollout scorer is capable of automatically calculating the total rollout duration, number of critical issues, -number of hotfixes, and number of rollbacks from build timelines and labeled core-eng issues. However, it still relies on -manual input to determine downtime and rollout failure. - -### Rollout failure -Rollout failure is a boolean value, and failed rollouts gain an extra fifty points. In the past, we have defined rollout -failure as "we sent out release notes for a rollout and then did not perform a rollout at all." Thus, the only rollout -previously categorized as a failure was the [4 September 2019 OSOB rollout](./Scorecard_2019-09-04.md). Under this definition, -rollout failure requires manual categorization since there is definitionally no evidence that a rollout was attempted. - -However, a more robust definition of rollout failure is "we did not roll out everything we promised in the release notes." -This still encompasses the previous definition, but additionally encompasses rollouts with partially successful deployments. -Anecdotally, both of the previous OSOB rollouts at time of writing fall under this definition, while there has only been one -rollout since we started tracking these that meets the earlier definition. - -This definition also has the benefit that it is significantly easier to measure – -**if the final build of a rollout isnot green, the rollout is categorized as a failed deployment.** - -### Downtime -Part of the reason downtime relied on manual input was that the definition itself is incredibly nebulous. -The [Rollout Scorecard's readme](./README.md) notes that downtime includes both "availability and reliability." - -The telemetry epic folks are expecting roll out alerting for our availability metrics for Helix and Maestro -(which should cover arcade-services measurements); these metrics are already measured [here](https://dotnet-eng-grafana.westus2.cloudapp.azure.com/d/quNLOchZz/service-availability?orgId=2&refresh=30s). -The next step should be to add alerting on metrics for reliability. Adding this alerting is outside the scope of this project, but some proposals for how it could be done are included below. - -
    -Alerting suggestions -
    -To begin with, we should start with an extremely broad definition of reliability downtime to capture the most catastrophic failures. -To this end, I propose the following alerting queries: - -* To measure OSOB downtime, look back over the past thirty five minutes and compare the queue depth to started jobs across all queues. -* **If there are more than an arbitrary number of queued jobs (e.g. 100) and zero started jobs, OSOB should be considered "down" and an alert will be triggered.** -* To measure Helix downtime, we will measure the status of the controller with the same query structure but compare service bus queue depth -* to queued jobs across all queues. -* **If service bus queue depth is greater than zero and there are zero queued jobs, Helix should be considered "down" and an alert will be triggered.** -
    - -Once alerting is in place, alerts will be filed as GitHub issues in core-eng. The Rollout Scorer is already pointed at core-eng for issues, -hotfixes, and rollbacks, and thus we can use these issues to determine downtime as well. Issues from these alerts can be automatically -tagged with a *Rollout Downtime* label and the appropriate repo rollout label. The Rollout Scorer will then measure downtime as -**the finish time of the build that ran prior to the opening of the alert to the time the issue is closed.** We could also use a different -event to indicate the end of a downtime period, such as leaving a comment on the issue saying "Downtime resolved at [time]" or something similar. -This has the additional benefit of allowing downtime issues to be manually filed if necessary, similar to the manual hotfix/rollout issues we already have. - -## Azure Function and Build Triggers - -The existing Rollout Scorer will be moved to arcade-services and deployed as an Azure Function. - -1. The function will poll the deployment table (see [dotnet/arcade-services#814](https://github.com/dotnet/arcade-services/pull/814)) for new entries -2. Upon finding a new deployment, it will set a timer for two days to allow a buffer for the rollout to complete fully. (This timer could be revised to a day if our rollout periods shorten. -3. As further entries come in (from other rollouts in the same period of time including both hotfixes and other repos), the timer will be reset and collect a list of repos to score. -4. Once the timer hits zero, the rollout scorer will score all the repos it received requests from with the start date set to the first day it received a request. As at present, it will create a PR to core-eng and add the scores to the database. - -This will effectively fully automate the rollout scorecard creation process. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CRollout_Scorer_Automation_Proposal.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CRollout_Scorer_Automation_Proposal.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CRollout_Scorer_Automation_Proposal.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Rollout_Scorer_Proposal.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Rollout_Scorer_Proposal.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Rollout_Scorer_Proposal.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Rollout_Scorer_Proposal.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,42 +0,0 @@ -# Rollout Scorer Proposal - -This is a proposal for the Rollout Scorer which will assist in generating rollout scorecards. Given a few inputs, it will scrape AzDO (and later, telemetry sources) to calculate a score and then generate a markdown file in a PR to core-eng and upload the data to Kusto. - -## Tool Description -The Rollout Scorer will be a command-line tool. The arguments it will accept are as follows: - -| Argument | Required? | Description | -|:------------------------------:|:------------:|:--------------------------------------| -| `--repo` or `-r` | **Required** | The repository to score | -| `--branch` or `-b` | *Optional* | The branch of the repo to score(e.g. servicing or prod); defaults to production | -| `--rollout-start-date` or `-s` | **Required** | The date on which the rollout started | -| `--rollout-end-date` or `-e` | *Optional* | The date on which the rollout ended; defaults to current date | -| `--number-of-rollbacks` | *Optional* | The number of rollbacks which occurred as part of the rollout; defaults to 0 | -| `--downtime` or `-d` | *Optional* | Specifies an amount of downtime which occurred | -| `--failed` or `-f` | *Optional* | Indicates a failed rollout (50 points) | -| `--output` or `-o` | *Optional* | File which the generated csv will be outputted to; defaults to `./scorecard.csv` | -| `--skip-output` | *Optional* | Skips the output step and directly uploads results | -| `--upload` or `-u` | *Optional* | Replaces all other parameters; uploads csv file to Kusto and makes PR in core-eng | - -The flow for using the Rollout Scorer is as follows: -* Run `RolloutScorer.exe` and specify the repo, rollout start date, and any optional parameters -* The Rollout Scorer will scrape AzDO for the appropriate data and create a CSV file containing the scorecard data -* User can make manual corrections to the CSV file as necessary -* Run `RolloutScorer.exe --upload {csv}` and the Rollout Scorer will upload the CSV file to Kusto and AzDO - -As shown in the parameters table, the user can optionally choose to skip the manual CSV adjustment stage. - -## Score Calculation -The Rollout Scorer will reference an INI file which will contain a map from repository name to the URI of the AzDO release or build definition. It will scrape this definition for all of the builds (targeting the production branch) or releases that occurred within the specified timeframe. From this data it will calculate: - -* **Total rollout time** — The sum of all build/release times -* **Number of critical issues** — Calculated from the number of commits in each hotfix release/build -* **Number of hotfixes** — Calculated from number of release/builds after the first one -* **Number of rollbacks** — Manually specified by the user -* **Downtime** — Manually specified by the user, but eventually will be calculated from telemetry -* **Failure to rollout** — Manually specified by the user - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CRollout_Scorer_Proposal.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CRollout_Scorer_Proposal.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CRollout_Scorer_Proposal.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-07-24.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-07-24.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-07-24.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-07-24.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,74 +0,0 @@ -# 24 July 2019 Rollout Summaries - -## Helix - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:------:| -| Time to Rollout | 05:14:06 | 0:30:00 | 19 | -| Critical/blocking issues created | 9 | 0 | 9 | -| Hotfixes | 5 | 0 | 25 | -| Rollbacks | 1 | 0 | 10 | -| Service downtime | 0:00:00 | 0:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| **Total** | | | **63** | - -## OS Onboarding - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:-------:| -| Time to Rollout | 22:47:26 | 1:00:00 | 88 | -| Critical/blocking issues created | 4 | 0 | 4 | -| Hotfixes | 4 | 0 | 20 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 0:00:00 | 0:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| **Total** | | | **112** | - -## Arcade Services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:------:| -| Time to Rollout | 03:11:00 | 1:00:00 | 9 | -| Critical/blocking issues created | 2 | 0 | 2 | -| Hotfixes | 2 | 0 | 10 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 0 | 0:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| **Total** | | | **21** | - -# Breakdowns - -## Helix - -| Metric | [2019072401.01](https://dev.azure.com/mseng/Tools/_releaseProgress?_a=release-pipeline-progress&releaseId=2672) | [2019072403.01](https://dev.azure.com/mseng/Tools/_releaseProgress?_a=release-pipeline-progress&releaseId=2673) | [2019072404.01](https://dev.azure.com/mseng/Tools/_releaseProgress?_a=release-pipeline-progress&releaseId=2675) | [2019072501.01](https://dev.azure.com/mseng/Tools/_releaseProgress?_a=release-pipeline-progress&releaseId=2676) | [2019072505.01](https://dev.azure.com/mseng/Tools/_releaseProgress?_a=release-pipeline-progress&releaseId=2678) | [2019072506.01](https://dev.azure.com/mseng/Tools/_releaseProgress?_a=release-pipeline-progress&releaseId=2679) | Total | -|:--------------------------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|:--------:| -| Time to Rollout | 2:07:08 | 0:28:10 | 0:28:16 | 0:32:02 | 0:37:00 | 1:01:30 | 05:14:06 | -| Critical/blocking issues resolved | 1
    SQL column | 1
    [c20e1561](https://dev.azure.com/mseng/Tools/_git/CoreFX%20Engineering%20Infrastructure/commit/c20e156129da8169c7d255bffd21ef142e254dae?refName=refs%2Fheads%2Fmaster) | 1
    [d0726c2f](https://dev.azure.com/mseng/Tools/_git/CoreFX%20Engineering%20Infrastructure/commit/d0726c2ff82299da276d0d874c5de9afde909eaf?refName=refs%2Fheads%2Fmaster) | 1
    [#7124](https://github.com/dotnet/core-eng/issues/7124)/[54f92c93](https://dev.azure.com/mseng/Tools/_git/CoreFX%20Engineering%20Infrastructure/commit/54f92c9389ee7bd0d31989f4594648e61b496fa9?refName=refs%2Fheads%2Fmaster) | 4
    [072d7af1](https://dev.azure.com/mseng/Tools/_git/CoreFX%20Engineering%20Infrastructure/commit/072d7af1be25f2a14c6f87b96e55b60df20d3134?refName=refs%2Fheads%2Fmaster)/[e5a768b8](https://dev.azure.com/mseng/Tools/_git/CoreFX%20Engineering%20Infrastructure/commit/e5a768b85ee03a0876ff54461373f4d0b9f72ca3?refName=refs%2Fheads%2Fmaster)
    [82a3f7d2](https://dev.azure.com/mseng/Tools/_git/CoreFX%20Engineering%20Infrastructure/commit/82a3f7d2f8b478c18fe0b9d14967fce35ce5baa5?refName=refs%2Fheads%2Fmaster)
    [c1fece1b](https://dev.azure.com/mseng/Tools/_git/CoreFX%20Engineering%20Infrastructure/commit/c1fece1b3af0aedc7772cbf78631efdebcbd9d39?refName=refs%2Fheads%2Fmaster)
    [dddb7d19](https://dev.azure.com/mseng/Tools/_git/CoreFX%20Engineering%20Infrastructure/commit/dddb7d19cddc0398e2d1a228b2a6a598a12a525a?refName=refs%2Fheads%2Fmaster) | 1
    [82b0cfba](https://dev.azure.com/mseng/Tools/_git/CoreFX%20Engineering%20Infrastructure/commit/82b0cfba1a618fc70a3f8f2c35604c6efa972b53?refName=refs%2Fheads%2Fmaster) | 9 | -| Hotfixes | 0 | 1 | 1 | 1 | 1 | 1 | 5 | -| Rollbacks | 0
      | 0
      | 0
      | 0
      | 1
    [072d7af1](https://dev.azure.com/mseng/Tools/_git/CoreFX%20Engineering%20Infrastructure/commit/072d7af1be25f2a14c6f87b96e55b60df20d3134?refName=refs%2Fheads%2Fmaster)/[e5a768b8](https://dev.azure.com/mseng/Tools/_git/CoreFX%20Engineering%20Infrastructure/commit/e5a768b85ee03a0876ff54461373f4d0b9f72ca3?refName=refs%2Fheads%2Fmaster) | 0
      | 1
      | -| Service downtime | 0:00:00 | 0:00:00 | 0:00:00 | 0:00:00 | 0:00:00 | 0:00:00 | 0:00:00 | - -## OS Onboarding - -| Metric | [2019072401](https://dev.azure.com/dnceng/internal/_build/results?buildId=277460) | [2019072402](https://dev.azure.com/dnceng/internal/_build/results?buildId=277925) | [2019072403](https://dev.azure.com/dnceng/internal/_build/results?buildId=278090) | [2019072404](https://dev.azure.com/dnceng/internal/_build/results?buildId=278216) | [2019072501](https://dev.azure.com/dnceng/internal/_build/results?buildId=279688) | [2019072601](https://dev.azure.com/dnceng/internal/_build/results?buildId=281352) | [2019072602](https://dev.azure.com/dnceng/internal/_build/results?buildId=281850) | Total | -|:--------------------------------:|:----------:|:----------:|:----------:|:----------:|:----------:|:----------:|:----------:|----------| -| Time to Rollout | 06:00:17 | 00:43:44 | 01:23:51 | 03:40:23 | 03:03:58 | 04:56:34 | 02:58:39 | 22:47:26 | -| Critical/blocking issues resolved | 0

      | 1
    [#7116](https://github.com/dotnet/core-eng/issues/7116)
      | 0

      | 1
    [d3f86f9f](https://dev.azure.com/dnceng/internal/_git/dotnet-helix-machines/commit/d3f86f9faf934a479eefc218e362b0b32c95dc1b?refName=refs%2Fheads%2Fdumps_documetation)
      | 1
    [7ee45aa7](https://dev.azure.com/dnceng/internal/_git/dotnet-helix-machines/commit/7ee45aa757941a7f0e07b84924f22f9171b2831f?refName=refs%2Fheads%2Fdumps_documetation)
      | 2
    [2bda61c6](https://dev.azure.com/dnceng/internal/_git/dotnet-helix-machines/commit/2bda61c6da08b3fd95aece392ccbe867d487b89b?refName=refs%2Fheads%2Fdumps_documetation)
    [72bc2a73](https://dev.azure.com/dnceng/internal/_git/dotnet-helix-machines/commit/72bc2a7391a3ca2e7f5ac40da39283b743ba16d4?refName=refs%2Fheads%2Fdumps_documetation) | 0

      | 5

      | -| Hotfixes | 0 | 1 | 1 | 1 | 1 | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 0:00:00 | 0:00:00 | 0:00:00 | 0:00:00 | 0:00:00 | 0:00:00 | 0:00:00 | 0:00:00 | - -## Arcade Services - -| Metric | [20190724.3-1](https://dev.azure.com/dnceng/internal/_releaseProgress?_a=release-pipeline-progress&releaseId=12364) | [20190725.3-1](https://dev.azure.com/dnceng/internal/_releaseProgress?_a=release-pipeline-progress&releaseId=12431) | [20190725.4-1](https://dev.azure.com/dnceng/internal/_releaseProgress?_a=release-pipeline-progress&releaseId=12455) |Total| -|:--------------:|:--------------:|:--------------:|:--------------:|:--:| -| Time to Rollout | 1:00:00 | 1:02:00 | 1:09:00 | 03:11:00 | -| Critical/blocking issues created | 0
      | 1
    [#547](https://github.com/dotnet/arcade-services/pull/547) | 1
    [#548](https://github.com/dotnet/arcade-services/pull/548) | 2 | -| Hotfixes | 0 | 1 | 1 | 2 | -| Rollbacks | 0 | 0 | 0 | 0 | -| Service downtime | 0:00:00 | 0:00:00 | 0:00:00 | 0:00:00 | - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-07-24.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-07-24.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-07-24.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-08-14.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-08-14.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-08-14.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-08-14.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,74 +0,0 @@ -# 14 August 2019 Rollout Summaries - -## Helix - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:---------:| -| Time to Rollout | 01:40:08 | 0:30:00 | 5 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 2 | 0 | 10 | -| Rollbacks | 1 | 0 | 10 | -| Service downtime | 04:51:00 | 0:00:00 | 291 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **317** | - -## OS Onboarding - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:---------:| -| Time to Rollout | 12:36:13 | 1:00:00 | 47 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 0:00:00 | 0:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **52** | - -## Arcade Services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:---------:| -| Time to Rollout | 01:02:00 | 1:00:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 0 | 0:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - -# Breakdowns - -## Helix - -| Metric | [2019081401.01](https://dev.azure.com/mseng/Tools/_releaseProgress?_a=release-pipeline-progress&releaseId=2690) | [2019081901.01](https://dev.azure.com/mseng/Tools/_releaseProgress?_a=release-pipeline-progress&releaseId=2693) | [2019081902.01](https://dev.azure.com/mseng/Tools/_releaseProgress?_a=release-pipeline-progress&releaseId=2694) | -|:--------------------------------:|:-------------:|:-------------:|:-------------:| -| Time to Rollout | 00:43:40 | 00:28:37 | 00:27:51 | -| Critical/blocking issues created | 1
    [#7438](https://github.com/dotnet/core-eng/issues/7438) | 0
      | 0
      | -| Hotfixes | 0 | 0 | 2 | -| Rollbacks | 1 | 0 | 0 | -| Service downtime | 0:00:00 | 4:51:00 | 0:00:00 | - -## OS Onboarding - -| Metric | [2019081901 #1](https://dev.azure.com/dnceng/internal/_build/results?buildId=315522) | [2019081901 #2](https://dev.azure.com/dnceng/internal/_build/results?buildId=315522) | [2019081901 #3](https://dev.azure.com/dnceng/internal/_build/results?buildId=315522) | -|:--------------------------------:|:-------------:|:-------------:|:-------------:| -| Time to Rollout | 05:12:40 | 03:21:07 | 04:02:26 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 1 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 0:00:00 | 0:00:00 | 0:00:00 | - -## Arcade Services - -| Metric | [20190815.1-1](https://dev.azure.com/dnceng/internal/_releaseProgress?_a=release-pipeline-progress&releaseId=15200) | -|:--------------------------------:|:------------:| -| Time to Rollout | 01:02:00 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 0:00:00 | - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-08-14.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-08-14.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-08-14.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-09-04.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-09-04.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-09-04.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-09-04.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,47 +0,0 @@ -# 04 September 2019 Rollout Summaries - -## Helix - -**NULL**—No rollout promised or occurred. - -## OS Onboarding - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:-------:| -| Time to Rollout | N/A | 0:00:00 | 0 | -| Critical/blocking issues created | N/A | 0 | 0 | -| Hotfixes | N/A | 0 | 0 | -| Rollbacks | N/A | 0 | 0 | -| Service downtime | N/A | 0:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| **Total** | | | **50** | - -Note: Perhaps this score should be considered NULL (like Helix) and the fifty should instead be rolled into the next rollout. - -## Arcade Services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:---------:| -| Time to Rollout | 02:06:00 | 1:00:00 | 5 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 0 | 0:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **11** | - -# Itemized Scorecards - -## Arcade Services - -| Metric | [20190904.2-1](https://dev.azure.com/dnceng/internal/_releaseProgress?_a=release-pipeline-progress&releaseId=17022) | [20190904.3-1](https://dev.azure.com/dnceng/internal/_releaseProgress?_a=release-pipeline-progress&releaseId=17041) | -|:--------------------------------:|:------------:|:-------------:| -| Time to Rollout | 01:01:00 | 01:05:00 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 0:00:00 | 0:00:00 | - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-09-04.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-09-04.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-09-04.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-09-25.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-09-25.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-09-25.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-09-25.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,75 +0,0 @@ -# 25 September 2019 Rollout Summaries - -## Helix - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:---------:| -| Time to Rollout | 01:25:00 | 0:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 0 | 0:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **9** | - - -## OS Onboarding - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:---------:| -| Time to Rollout | 04:57:00 | 1:00:00 | 16 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 0 | 0:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **16** | - -## Arcade Services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:---------:| -| Time to Rollout | 01:22:00 | 1:00:00 | 2 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 0 | 0:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - -# Itemized Scorecards - -## Helix - -| Metric | [2019092504](https://dev.azure.com/mseng/Tools/_build/results?buildId=10509391&view=results) | [2019092505](https://dev.azure.com/mseng/Tools/_build/results?buildId=10509665&view=results) | -|:--------------------------------:|:------------:|:-------------:| -| Time to Rollout | 00:32:35 | 00:53:24 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 0:00:00 | 0:00:00 | - -## OS Onboarding - -| Metric | [20190925.01](https://dev.azure.com/dnceng/internal/_build/results?buildId=365568) | -|:--------------------------------:|:------------:| -| Time to Rollout | 04:57:00 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 0:00:00 | - - -## Arcade Services - -| Metric | [20190924.1-1](https://dev.azure.com/dnceng/internal/_releaseProgress?_a=release-pipeline-progress&releaseId=19532) | [20190925.3-1](https://dev.azure.com/dnceng/internal/_releaseProgress?_a=release-pipeline-progress&releaseId=19608) | -|:--------------------------------:|:------------:|:-------------:| -| Time to Rollout | 00:42:00 | 00:40:00 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 0:00:00 | 0:00:00 | - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-09-25.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-09-25.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-09-25.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-10-02.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-10-02.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-10-02.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-10-02.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ -# 02 October 2019 Rollout Summaries - -## Arcade Services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:---------:| -| Time to Rollout | 01:03:00 | 1:00:00 | 1 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 0 | 0:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **7** | - -# Itemized Scorecards - -## Arcade Services - -| Metric | [20191002.1-1](https://dev.azure.com/dnceng/internal/_releaseProgress?_a=release-pipeline-progress&releaseId=20235) | [20191002.3-1](https://dev.azure.com/dnceng/internal/_releaseProgress?_a=release-pipeline-progress&releaseId=20259) | -|:--------------------------------:|:------------:|:-------------:| -| Time to Rollout | 00:31:00 | 00:32:00 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 0:00:00 | 0:00:00 | - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-10-02.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-10-02.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-10-02.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-10-16.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-10-16.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-10-16.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-10-16.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ -# 16 October 2019 Rollout Summaries - -## Arcade Services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:---------:| -| Time to Rollout | 01:40:00 | 1:00:00 | 3 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 0 | 0:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **9** | - -# Itemized Scorecards - -## Arcade Services - -| Metric | [20191016.2-1](https://dnceng.visualstudio.com/internal/_apps/hub/ms.vss-releaseManagement-web.cd-release-progress?_a=release-pipeline-progress&releaseId=21564) | [20191016.3-1](https://dnceng.visualstudio.com/internal/_apps/hub/ms.vss-releaseManagement-web.cd-release-progress?_a=release-pipeline-progress&releaseId=21602) | -|:--------------------------------:|:------------:|:-------------:| -| Time to Rollout | 01:00:00 | 00:40:00 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 0:00:00 | 0:00:00 | - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-10-16.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-10-16.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-10-16.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-10-30.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-10-30.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-10-30.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-10-30.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,52 +0,0 @@ -# 30 October 2019 Rollout Summaries - -## Helix - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:---------:| -| Time to Rollout | 00:50:13 | 0:30:00 | 2 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 0 | 0:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - -## Arcade Services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:-------:|:---------:| -| Time to Rollout | 00:59:00 | 1:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 0 | 0:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - -# Itemized Scorecards - -## Helix - -| Metric | [2019103001](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=407852) | [2019103002](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=407861) | [2019103003](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=408059) | [2019103008](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=408519) | -|:--------------------------------:|:------------:|:-------------:|:-------------:|:-------------:| -| Time to Rollout | 00:00:00 | 00:00:00 | 00:48:21 | 00:01:53 | -| Critical/blocking issues created | 1 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | 1 | -| Rollbacks | 0 | 0 | 0 | 0 | -| Service downtime | 0:00:00 | 0:00:00 | 0:00:00 | 0:00:00 | - -## Arcade Services - -| Metric | [20191002.1-1](https://dev.azure.com/dnceng/internal/_releaseProgress?_a=release-pipeline-progress&releaseId=20235) | -|:--------------------------------:|:-------------:| -| Time to Rollout | 00:59:00 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 0:00:00 | - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-10-30.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-10-30.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-10-30.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-11-13.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-11-13.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-11-13.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-11-13.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,30 +0,0 @@ -# 13 November 2019 Rollout Summaries - -## OS Onboarding - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:33:02 | 01:00:00 | 7 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **63** | - -# Itemized Scorecards - -## OS Onboarding - -| Metric | [20191113.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=426238) | -|:--------------------------------:|:-------------:| -| Time to Rollout | 02:33:02 | -| Critical/blocking issues created | 1 | -| Hotfixes | 1 | -| Rollbacks | 0 | -| Service downtime | 0:00:00 | - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-11-13.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-11-13.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-11-13.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-12-04.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-12-04.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-12-04.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-12-04.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 04 December 2019 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:09:54 | 01:00:00 | 5 | -| Critical/blocking issues created | 2 | 0 | 2 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **57** | - -Relevant GitHub issues: [#8460](https://github.com/dotnet/core-eng/issues/8460), [#8462](https://github.com/dotnet/core-eng/issues/8462) -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20191204.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=446510) | -|:-----:|:-----:| -| Time to Rollout | 02:09:54 | -| Critical/blocking issues created | 2 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-12-04.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-12-04.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-12-04.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-12-11.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-12-11.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-12-11.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2019-12-11.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 11 December 2019 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:42:12 | 00:30:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2019121103](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=454056) | -|:-----:|:-----:| -| Time to Rollout | 00:42:12 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-12-11.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-12-11.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2019-12-11.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-01-09.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-01-09.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-01-09.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-01-09.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 09 January 2020 Rollout Summaries - -## dotnet-arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:17:22 | 00:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 13:12:17 | 01:00:00 | 49 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 3 | 0 | 15 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **64** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:23:08 | 00:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 2 | 0 | 10 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **14** | - - -# Itemized Scorecard - -## dotnet-arcade-services - -| Metric | [20200109.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=478054) | [20200109.11](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=478055) | [20200109.12](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=478056) | [20200109.13](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=478057) | [20200109.15](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=478077) | [20200109.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=477962) | [20200109.4](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=478046) | [20200109.5](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=478048) | [20200109.6](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=478049) | [20200109.7](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=478050) | [20200109.8](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=478052) | [20200109.9](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=478053) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 01:17:22 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | -| Critical/blocking issues created | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20200109.07](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=477958) | [20200114.12](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=483093) | [20200115.17](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=484673) | [20200117.33](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=488746) | -|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 04:33:00 | 03:10:30 | 03:42:23 | 01:46:24 | -| Critical/blocking issues created | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 1 | 1 | 1 | -| Rollbacks | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [20200109.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=477930) | [20200109.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=478195) | [2020010901](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=477904) | [2020010902](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=478150) | [20200110.5](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=479292) | [2020011006](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=479250) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 00:02:29 | 00:12:06 | 00:17:50 | 00:17:23 | 00:13:49 | 00:19:31 | -| Critical/blocking issues created | 0 | 0 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 1 | 0 | 0 | 1 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-01-09.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-01-09.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-01-09.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-01-23.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-01-23.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-01-23.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-01-23.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 23 January 2020 Rollout Summaries - -## dotnet-arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:41:53 | 00:30:00 | 5 | -| Critical/blocking issues created | 3 | 0 | 3 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 0 | -| Total | | | **8** | - -Relevant GitHub issues: [#8638](https://github.com/dotnet/core-eng/issues/8638), [#8639](https://github.com/dotnet/core-eng/issues/8639), [#8645](https://github.com/dotnet/core-eng/issues/8645) -# Itemized Scorecard - -## dotnet-arcade-services - -| Metric | [20200123.10](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=495249) | [20200123.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=494298) | [20200123.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=494322) | [20200123.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=494517) | [20200123.4](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=494518) | [20200123.9](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=495165) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 00:23:07 | 00:27:23 | 00:00:00 | 00:21:14 | 00:00:03 | 00:30:06 | -| Critical/blocking issues created | 3 | 0 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-01-23.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-01-23.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-01-23.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-01-29.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-01-29.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-01-29.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-01-29.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,57 +0,0 @@ -# 29 January 2020 Rollout Summaries - -## dotnet-arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:04:58 | 00:30:00 | 3 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - -Relevant issue: [#8695](https://github.com/dotnet/core-eng/issues/8965) - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:47:36 | 00:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -# Itemized Scorecard - -## dotnet-arcade-services - -| Metric | [20200129.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=500617) | [20200129.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=500652) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:44:05 | 00:20:53 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [20200129.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=500752) | [2020012902](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=500707) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:28:31 | 00:19:05 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-01-29.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-01-29.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-01-29.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-02-05.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-02-05.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-02-05.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-02-05.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 05 February 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:57:22 | 01:00:00 | 12 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **12** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200205.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=508709) | -|:-----:|:-----:| -| Time to Rollout | 03:57:22 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-02-05.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-02-05.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-02-05.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-02-12.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-02-12.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-02-12.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-02-12.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 12 February 2020 Rollout Summaries - -## dotnet-arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:50:42 | 00:30:00 | 2 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - -Relevant GitHub issues: [#8965](https://github.com/dotnet/core-eng/issues/8965) -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:43:30 | 01:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-arcade-services - -| Metric | [20200212.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=517794) | [20200212.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=517824) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:28:46 | 00:21:56 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20200212.15](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=518035) | -|:-----:|:-----:| -| Time to Rollout | 00:43:30 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-02-12.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-02-12.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-02-12.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-02-19.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-02-19.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-02-19.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-02-19.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 19 February 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:49:01 | 00:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20200219.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=527970) | -|:-----:|:-----:| -| Time to Rollout | 00:49:01 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-02-19.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-02-19.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-02-19.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-03-18.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-03-18.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-03-18.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-03-18.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 18 March 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:09:42 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20200318.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=563929) | -|:-----:|:-----:| -| Time to Rollout | 01:09:42 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-03-18.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-03-18.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-03-18.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-03-25.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-03-25.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-03-25.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-03-25.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 25 March 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:24:27 | 00:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:50:46 | 00:30:00 | 10 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **10** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20200325.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=572792) | -|:-----:|:-----:| -| Time to Rollout | 01:24:27 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020032501](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=572789) | -|:-----:|:-----:| -| Time to Rollout | 02:50:46 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-03-25.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-03-25.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-03-25.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-01.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-01.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-01.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-01.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,82 +0,0 @@ -# 01 April 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:20:31 | 00:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:47:04 | 00:30:00 | 6 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **7** | - -Relevant GitHub issues: [#9527](https://github.com/dotnet/core-eng/issues/9527) - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:40:06 | 01:00:00 | 7 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **7** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20200401.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=583927) | -|:-----:|:-----:| -| Time to Rollout | 01:20:31 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020040101](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=583655) | -|:-----:|:-----:| -| Time to Rollout | 01:47:04 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20200401.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=583559) | [20200401.10](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=583973) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:17:49 | 02:22:17 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-01.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-01.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-01.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-06.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-06.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-06.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-06.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 06 April 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:23:44 | 01:00:00 | 14 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **14** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200406.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=590312) | [20200408.09](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=593447) | [20200409.08](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=594811) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 00:50:04 | 02:47:59 | 00:45:41 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-06.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-06.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-06.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-08.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-08.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-08.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-08.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 08 April 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:56:27 | 00:30:00 | 6 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **6** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:32:23 | 00:30:00 | 13 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **18** | - -Relevant GitHub issues: [#9581](https://github.com/dotnet/core-eng/issues/9581) -# Itemized Scorecard - -## arcade-services - -| Metric | [20200408.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=593231) | -|:-----:|:-----:| -| Time to Rollout | 01:56:27 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020040801](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=593215) | [2020040802](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=593432) | [2020040803](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=593443) | [2020040804](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=593475) | [2020040805](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=593592) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 01:20:39 | 00:04:20 | 00:13:06 | 01:05:51 | 00:48:27 | -| Critical/blocking issues created | 0 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-08.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-08.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-08.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-15.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-15.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-15.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-15.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 15 April 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:49:26 | 01:00:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:13:26 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200415.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=601748) | -|:-----:|:-----:| -| Time to Rollout | 01:49:26 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20200415.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=602285) | -|:-----:|:-----:| -| Time to Rollout | 01:13:26 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-15.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-15.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-15.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-22.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-22.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-22.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-22.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 22 April 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:54:46 | 01:00:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:24:41 | 00:30:00 | 8 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200422.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=611525) | -|:-----:|:-----:| -| Time to Rollout | 01:54:46 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20200422.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=611843) | -|:-----:|:-----:| -| Time to Rollout | 02:24:41 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-22.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-22.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-22.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-29.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-29.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-29.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-04-29.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 29 April 2020 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:43:46 | 00:30:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:52:38 | 01:00:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2020042901](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=623343) | -|:-----:|:-----:| -| Time to Rollout | 00:43:46 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20200429.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=622846) | -|:-----:|:-----:| -| Time to Rollout | 01:52:38 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-29.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-29.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-04-29.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-06.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-06.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-06.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-06.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 06 May 2020 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:47:44 | 00:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:10:40 | 01:00:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:04:48 | 00:30:00 | 11 | -| Critical/blocking issues created | 2 | 0 | 2 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **68** | - -Relevant GitHub issues: [#9760](https://github.com/dotnet/core-eng/issues/9760), [#9758](https://github.com/dotnet/core-eng/issues/9758) -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2020050602](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=633413) | -|:-----:|:-----:| -| Time to Rollout | 00:47:44 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20200506.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=633216) | -|:-----:|:-----:| -| Time to Rollout | 01:10:40 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20200506.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=633419) | [20200506.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=633471) | [20200506.6](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=633978) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 00:00:00 | 01:04:08 | 02:00:40 | -| Critical/blocking issues created | 2 | 0 | 0 | -| Hotfixes | 0 | 1 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-05-06.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-05-06.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-05-06.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-13.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-13.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-13.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-13.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 13 May 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:53:06 | 01:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:46:22 | 00:30:00 | 6 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **6** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:41:02 | 00:30:00 | 9 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **9** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200513.09](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=642899) | -|:-----:|:-----:| -| Time to Rollout | 00:53:06 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20200513.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=643086) | -|:-----:|:-----:| -| Time to Rollout | 01:46:22 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020051301](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=643095) | -|:-----:|:-----:| -| Time to Rollout | 02:41:02 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-05-13.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-05-13.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-05-13.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-20.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-20.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-20.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-20.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 20 May 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:46:38 | 01:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:20:51 | 00:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200520.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=653406) | -|:-----:|:-----:| -| Time to Rollout | 00:46:38 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020052001](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=653601) | -|:-----:|:-----:| -| Time to Rollout | 01:20:51 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-05-20.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-05-20.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-05-20.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-27.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-27.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-27.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-05-27.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 27 May 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:49:33 | 01:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200527.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=661191) | -|:-----:|:-----:| -| Time to Rollout | 00:49:33 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-05-27.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-05-27.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-05-27.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-03.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-03.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-03.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-03.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 03 June 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:00:56 | 01:00:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:10:53 | 00:30:00 | 7 | -| Critical/blocking issues created | 2 | 0 | 2 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **14** | - -Relevant GitHub issues: [#9994](https://github.com/dotnet/core-eng/issues/9994), [#9986](https://github.com/dotnet/core-eng/issues/9986) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:51:50 | 00:30:00 | 14 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **15** | - -Relevant GitHub issues: [#9989](https://github.com/dotnet/core-eng/issues/9989) -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200603.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=669847) | -|:-----:|:-----:| -| Time to Rollout | 01:00:56 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20200603.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=669999) | [20200603.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=670290) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:10:04 | 01:00:49 | -| Critical/blocking issues created | 5 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020060301](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=670017) | [2020060302](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=670314) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 02:36:14 | 01:15:36 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-06-03.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-06-03.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-06-03.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-10.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-10.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-10.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-10.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 10 June 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:19:32 | 01:00:00 | 2 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - -Relevant GitHub issues: [#10044](https://github.com/dotnet/core-eng/issues/10044) -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200610.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=680186) | -|:-----:|:-----:| -| Time to Rollout | 01:19:32 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-06-10.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-06-10.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-06-10.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-17.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-17.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-17.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-17.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 17 June 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:27:39 | 01:00:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:00:05 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:15:55 | 00:30:00 | 8 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **14** | - -Relevant GitHub issues: [#10072](https://github.com/dotnet/core-eng/issues/10072) -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200617.05](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=691653) | -|:-----:|:-----:| -| Time to Rollout | 01:27:39 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020061701](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=691951) | -|:-----:|:-----:| -| Time to Rollout | 01:00:05 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20200617.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=691952) | [20200617.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=692240) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:14:06 | 01:01:49 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-06-17.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-06-17.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-06-17.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-25.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-25.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-25.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-06-25.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 25 June 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:00:21 | 01:00:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200625.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=704332) | -|:-----:|:-----:| -| Time to Rollout | 01:00:21 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-06-25.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-06-25.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-06-25.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-01.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-01.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-01.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-01.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 01 July 2020 Rollout Summaries - -## dotnet-arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:11:54 | 00:30:00 | 7 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - -Relevant GitHub issues: [#10213](https://github.com/dotnet/core-eng/issues/10213) -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 05:24:23 | 01:00:00 | 18 | -| Critical/blocking issues created | 3 | 0 | 3 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **26** | - -Relevant GitHub issues: [#10182](https://github.com/dotnet/core-eng/issues/10182), [#10183](https://github.com/dotnet/core-eng/issues/10183), [#10184](https://github.com/dotnet/core-eng/issues/10184) -# Itemized Scorecard - -## dotnet-arcade-services - -| Metric | [20200701.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=713174) | [20200706.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=718277) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:11:46 | 01:00:08 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20200701.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=712749) | [20200701.05](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=713544) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 04:57:12 | 00:27:11 | -| Critical/blocking issues created | 3 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-01.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-01.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-01.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-08.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-08.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-08.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-08.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 08 July 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:56:25 | 01:00:00 | 0 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - -Relevant GitHub issues: [#10234](https://github.com/dotnet/core-eng/issues/10234) -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200708.06](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=720859) | -|:-----:|:-----:| -| Time to Rollout | 00:56:25 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-08.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-08.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-08.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-15.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-15.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-15.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-15.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 15 July 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:36:47 | 01:00:00 | 3 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **9** | - -Relevant GitHub issues: [#10263](https://github.com/dotnet/core-eng/issues/10263), [#10264](https://github.com/dotnet/core-eng/issues/10264) -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:00:17 | 00:30:00 | 7 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **7** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200715.06](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=730843) | -|:-----:|:-----:| -| Time to Rollout | 01:36:47 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20200715.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=731051) | -|:-----:|:-----:| -| Time to Rollout | 02:00:17 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-15.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-15.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-15.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-22.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-22.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-22.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-22.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 22 July 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:15:38 | 00:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:28:04 | 01:00:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **7** | - -Relevant GitHub issues: [#10296](https://github.com/dotnet/core-eng/issues/10296) -# Itemized Scorecard - -## arcade-services - -| Metric | [20200722.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=741433) | -|:-----:|:-----:| -| Time to Rollout | 01:15:38 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20200722.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=741141) | -|:-----:|:-----:| -| Time to Rollout | 01:28:04 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-22.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-22.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-22.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-29.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-29.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-29.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-07-29.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 29 July 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:09:13 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:06:25 | 01:00:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:09:45 | 00:30:00 | 7 | -| Critical/blocking issues created | 2 | 0 | 2 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 1 | 0 | 10 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **69** | - -Relevant GitHub issues: [#10348](https://github.com/dotnet/core-eng/issues/10348), [#10349](https://github.com/dotnet/core-eng/issues/10349) -# Itemized Scorecard - -## arcade-services - -| Metric | [20200729.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=749990) | -|:-----:|:-----:| -| Time to Rollout | 01:09:13 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20200729.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=749610) | -|:-----:|:-----:| -| Time to Rollout | 01:06:25 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020072903](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=749988) | [2020072908](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=750570) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:13:11 | 00:56:34 | -| Critical/blocking issues created | 2 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 1 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-29.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-29.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-07-29.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-05.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-05.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-05.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-05.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 05 August 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:09:09 | 01:00:00 | 9 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **9** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200805.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=757876) | -|:-----:|:-----:| -| Time to Rollout | 03:09:09 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-05.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-05.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-05.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-12.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-12.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-12.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-12.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 12 August 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:03:40 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:51:52 | 01:00:00 | 8 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 2 | 0 | 10 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **69** | - -Relevant GitHub issues: [#10432](https://github.com/dotnet/core-eng/issues/10432), [#10457](https://github.com/dotnet/core-eng/issues/10457) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:05:57 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20200812.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=767162) | -|:-----:|:-----:| -| Time to Rollout | 01:03:40 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20200812.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=766774) | [20200813.07](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=769425) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:36:21 | 01:15:31 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020081201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=767201) | -|:-----:|:-----:| -| Time to Rollout | 01:05:57 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-12.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-12.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-12.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-19.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-19.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-19.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-19.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 19 August 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:12:29 | 00:30:00 | 3 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - -Relevant GitHub issues: [#10553](https://github.com/dotnet/core-eng/issues/10553) -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:33:40 | 01:00:00 | 15 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **21** | - -Relevant GitHub issues: [#10577](https://github.com/dotnet/core-eng/issues/10577) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:23:52 | 00:30:00 | 8 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **13** | - -Relevant GitHub issues: [#10549](https://github.com/dotnet/core-eng/issues/10549) -# Itemized Scorecard - -## arcade-services - -| Metric | [20200819.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=778122) | -|:-----:|:-----:| -| Time to Rollout | 01:12:29 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20200819.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=777637) | [20200820.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=781114) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 03:59:44 | 00:33:56 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020081901](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=778157) | -|:-----:|:-----:| -| Time to Rollout | 02:23:52 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-19.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-19.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-19.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-21.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-21.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-21.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-21.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 21 August 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:33:56 | 01:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200820.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=781114) | -|:-----:|:-----:| -| Time to Rollout | 00:33:56 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-21.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-21.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-21.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-26.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-26.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-26.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-08-26.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 26 August 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:07:26 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20200826.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=788669) | -|:-----:|:-----:| -| Time to Rollout | 01:07:26 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-26.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-26.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-08-26.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-02.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-02.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-02.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-02.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 02 September 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:13:16 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:40:10 | 01:00:00 | 7 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **13** | - -Relevant GitHub issues: [#10701](https://github.com/dotnet/core-eng/issues/10701) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:12:48 | 00:30:00 | 15 | -| Critical/blocking issues created | 2 | 0 | 2 | -| Hotfixes | 2 | 0 | 10 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **27** | - -Relevant GitHub issues: [#10700](https://github.com/dotnet/core-eng/issues/10700), [#10708](https://github.com/dotnet/core-eng/issues/10708) -# Itemized Scorecard - -## arcade-services - -| Metric | [20200902.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=798796) | -|:-----:|:-----:| -| Time to Rollout | 01:13:16 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20200902.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=798436) | [20200902.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=799239) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 02:04:11 | 00:35:59 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020090201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=798797) | [2020090202](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=798957) | [2020090204](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=799087) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 01:11:26 | 01:16:07 | 01:45:15 | -| Critical/blocking issues created | 2 | 0 | 0 | -| Hotfixes | 0 | 1 | 1 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-02.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-02.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-02.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-09.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-09.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-09.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-09.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 09 September 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:00:33 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:29:30 | 01:00:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:05:08 | 00:30:00 | 3 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - -Relevant GitHub issues: [#10759](https://github.com/dotnet/core-eng/issues/10759) -# Itemized Scorecard - -## arcade-services - -| Metric | [20200909.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=807497) | -|:-----:|:-----:| -| Time to Rollout | 01:00:33 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20200909.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=807116) | -|:-----:|:-----:| -| Time to Rollout | 01:29:30 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020090901](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=807483) | -|:-----:|:-----:| -| Time to Rollout | 01:05:08 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-09.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-09.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-09.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-16.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-16.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-16.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-16.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 16 September 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:56:52 | 00:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20200916.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=817874) | -|:-----:|:-----:| -| Time to Rollout | 00:56:52 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-16.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-16.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-16.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-17.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-17.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-17.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-17.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 17 September 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:45:53 | 01:00:00 | 4 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **5** | - -Relevant GitHub issues: [#10849](https://github.com/dotnet/core-eng/issues/10849) -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200917.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=819350) | -|:-----:|:-----:| -| Time to Rollout | 01:45:53 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-17.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-17.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-17.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-23.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-23.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-23.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-23.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 23 September 2020 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:09:24 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2020092301](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=827545) | -|:-----:|:-----:| -| Time to Rollout | 01:09:24 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-23.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-23.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-23.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-24.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-24.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-24.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-24.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 24 September 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 05:50:30 | 01:00:00 | 20 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **70** | - -Relevant GitHub issues: [#11097](https://github.com/dotnet/core-eng/issues/11097) -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20200924.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=829258) | -|:-----:|:-----:| -| Time to Rollout | 05:50:30 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-24.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-24.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-24.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-30.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-30.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-30.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-09-30.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 30 September 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:08:13 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:33:06 | 01:00:00 | 7 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **7** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:10:54 | 00:30:00 | 7 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **12** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20200930.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=837196) | -|:-----:|:-----:| -| Time to Rollout | 01:08:13 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20200930.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=836971) | -|:-----:|:-----:| -| Time to Rollout | 02:33:06 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020093001](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=837200) | [2020093008](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=837799) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:04:24 | 01:06:30 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-30.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-30.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-09-30.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-07.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-07.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-07.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-07.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 07 October 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:19:20 | 00:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 09:33:14 | 01:00:00 | 35 | -| Critical/blocking issues created | 2 | 0 | 2 | -| Hotfixes | 5 | 0 | 25 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **62** | - -Relevant GitHub issues: [#11100](https://github.com/dotnet/core-eng/issues/11100), [#11105](https://github.com/dotnet/core-eng/issues/11105) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:09:28 | 00:30:00 | 7 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **7** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20201007.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=844165) | -|:-----:|:-----:| -| Time to Rollout | 01:19:20 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20201007.07](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=843892) | [20201007.12](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=843995) | [20201007.14](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=844239) | [20201007.15](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=844559) | [20201008.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=845582) | [20201008.13](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=846244) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 01:18:59 | 02:55:05 | 03:22:22 | 00:43:08 | 00:33:25 | 00:40:15 | -| Critical/blocking issues created | 2 | 0 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 1 | 1 | 1 | 1 | 1 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020100701](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=844203) | -|:-----:|:-----:| -| Time to Rollout | 02:09:28 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-10-07.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-10-07.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-10-07.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-14.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-14.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-14.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-14.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 14 October 2020 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:13:30 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:10:10 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:35:46 | 01:00:00 | 7 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **13** | - -Relevant GitHub issues: [#11188](https://github.com/dotnet/core-eng/issues/11188) -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2020101401](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=852715) | -|:-----:|:-----:| -| Time to Rollout | 01:13:30 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20201014.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=852863) | -|:-----:|:-----:| -| Time to Rollout | 01:10:10 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20201014.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=852824) | [20201014.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=853066) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:44:03 | 00:51:43 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-10-14.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-10-14.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-10-14.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-21.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-21.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-21.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-21.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 21 October 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:39:51 | 01:00:00 | 7 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **13** | - -Relevant GitHub issues: [#11257](https://github.com/dotnet/core-eng/issues/11257) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:02:48 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20201021.11](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=861267) | [20201022.09](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=863122) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:56:11 | 00:43:40 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020102101](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=861373) | -|:-----:|:-----:| -| Time to Rollout | 01:02:48 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-10-21.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-10-21.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-10-21.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-28.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-28.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-28.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-10-28.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 28 October 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:43:21 | 00:30:00 | 5 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **5** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:20:13 | 01:00:00 | 14 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **14** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20201028.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=869120) | -|:-----:|:-----:| -| Time to Rollout | 01:43:21 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20201028.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=868967) | [20201028.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=868993) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:13:02 | 04:07:11 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-10-28.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-10-28.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-10-28.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-04.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-04.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-04.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-04.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 04 November 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:44:04 | 01:00:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:05:56 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:03:50 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20201104.08](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=875575) | -|:-----:|:-----:| -| Time to Rollout | 01:44:04 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20201104.6](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=875677) | -|:-----:|:-----:| -| Time to Rollout | 01:05:56 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020110401](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=875675) | -|:-----:|:-----:| -| Time to Rollout | 01:03:50 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-11-04.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-11-04.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-11-04.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-10.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-10.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-10.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-10.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 10 November 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:51:24 | 01:00:00 | 8 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20201110.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=881163) | [20201111.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=882626) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:57:25 | 01:53:59 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-11-10.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-11-10.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-11-10.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-11.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-11.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-11.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-11.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 11 November 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:52:42 | 00:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20201111.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=882955) | -|:-----:|:-----:| -| Time to Rollout | 00:52:42 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-11-11.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-11-11.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-11-11.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-19.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-19.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-19.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-11-19.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 19 November 2020 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:00:59 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20201119.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=892878) | -|:-----:|:-----:| -| Time to Rollout | 01:00:59 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-11-19.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-11-19.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-11-19.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-12-02.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-12-02.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-12-02.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-12-02.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 02 December 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:57:56 | 01:00:00 | 8 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:14:51 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:16:22 | 00:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20201202.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=905636) | -|:-----:|:-----:| -| Time to Rollout | 02:57:56 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20201202.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=906076) | -|:-----:|:-----:| -| Time to Rollout | 01:14:51 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2020120201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=906081) | -|:-----:|:-----:| -| Time to Rollout | 01:16:22 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-12-02.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-12-02.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-12-02.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-12-09.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-12-09.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-12-09.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-12-09.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 09 December 2020 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 1.11:36:35 | 01:00:00 | 139 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **144** | - -Relevant GitHub issues: [#11661](https://github.com/dotnet/core-eng/issues/11661) -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20201209.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=914301) | [20201210.12](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=916909) | [20201216.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=923484) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 1.08:34:39 | 00:31:36 | 02:30:20 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-12-09.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-12-09.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-12-09.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-12-16.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-12-16.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-12-16.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2020-12-16.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 16 December 2020 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:09:27 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:02:30 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2020121601](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=923706) | -|:-----:|:-----:| -| Time to Rollout | 01:09:27 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20201216.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=923756) | -|:-----:|:-----:| -| Time to Rollout | 01:02:30 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-12-16.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-12-16.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2020-12-16.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-06.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-06.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-06.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-06.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 06 January 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:02:26 | 00:30:00 | 7 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - -Relevant GitHub issues: [#11813](https://github.com/dotnet/core-eng/issues/11813) -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:29:56 | 01:00:00 | 6 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **57** | - -Relevant GitHub issues: [#11815](https://github.com/dotnet/core-eng/issues/11815) -# Itemized Scorecard - -## arcade-services - -| Metric | [20210106.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=940342) | -|:-----:|:-----:| -| Time to Rollout | 02:02:26 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20210106.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=940115) | -|:-----:|:-----:| -| Time to Rollout | 02:29:56 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-01-06.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-01-06.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-01-06.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-13.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-13.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-13.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-13.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 13 January 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:31:19 | 00:30:00 | 9 | -| Critical/blocking issues created | 2 | 0 | 2 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **61** | - -Relevant GitHub issues: [#11854](https://github.com/dotnet/core-eng/issues/11854), [#11856](https://github.com/dotnet/core-eng/issues/11856) -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:12:58 | 01:00:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:43:57 | 00:30:00 | 5 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **6** | - -Relevant GitHub issues: [#11853](https://github.com/dotnet/core-eng/issues/11853) -# Itemized Scorecard - -## arcade-services - -| Metric | [20210113.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=948517) | [20210113.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=948811) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:35:41 | 00:55:38 | -| Critical/blocking issues created | 2 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20210113.06](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=948233) | -|:-----:|:-----:| -| Time to Rollout | 01:12:58 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021011301](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=948516) | -|:-----:|:-----:| -| Time to Rollout | 01:43:57 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-01-13.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-01-13.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-01-13.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-20.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-20.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-20.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-20.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 20 January 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:01:11 | 01:00:00 | 5 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **5** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:01:12 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210120.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=958380) | -|:-----:|:-----:| -| Time to Rollout | 02:01:11 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021012001](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=958572) | -|:-----:|:-----:| -| Time to Rollout | 01:01:12 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-01-20.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-01-20.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-01-20.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-27.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-27.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-27.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-01-27.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 27 January 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:38:51 | 01:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210127.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=968041) | -|:-----:|:-----:| -| Time to Rollout | 00:38:51 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-01-27.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-01-27.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-01-27.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-03.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-03.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-03.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-03.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 03 February 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:59:53 | 00:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:02:41 | 01:00:00 | 5 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **5** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20210203.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=977417) | -|:-----:|:-----:| -| Time to Rollout | 00:59:53 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20210203.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=977010) | -|:-----:|:-----:| -| Time to Rollout | 02:02:41 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-02-03.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-02-03.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-02-03.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-10.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-10.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-10.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-10.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 10 February 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:18:09 | 00:30:00 | 8 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:11:18 | 01:00:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:04:51 | 00:30:00 | 7 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **13** | - -Relevant GitHub issues: [#12169](https://github.com/dotnet/core-eng/issues/12169) -# Itemized Scorecard - -## arcade-services - -| Metric | [20210210.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=988245) | [20210210.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=988552) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:20:47 | 00:57:22 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20210210.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=987462) | -|:-----:|:-----:| -| Time to Rollout | 01:11:18 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021021003](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=988235) | [2021021201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=993070) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:04:48 | 01:00:03 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-02-10.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-02-10.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-02-10.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-17.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-17.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-17.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-17.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 17 February 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:58:39 | 00:30:00 | 6 | -| Critical/blocking issues created | 2 | 0 | 2 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **13** | - -Relevant GitHub issues: [#12215](https://github.com/dotnet/core-eng/issues/12215), [#12216](https://github.com/dotnet/core-eng/issues/12216) -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:43:46 | 01:00:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:06:52 | 00:30:00 | 7 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - -Relevant GitHub issues: [#12215](https://github.com/dotnet/core-eng/issues/12215) -# Itemized Scorecard - -## arcade-services - -| Metric | [20210217.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=998715) | [20210217.4](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=999169) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:03:27 | 00:55:12 | -| Critical/blocking issues created | 2 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20210217.05](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=998480) | -|:-----:|:-----:| -| Time to Rollout | 01:43:46 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021021701](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=998713) | -|:-----:|:-----:| -| Time to Rollout | 02:06:52 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-02-17.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-02-17.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-02-17.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-24.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-24.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-24.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-02-24.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 24 February 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:58:37 | 00:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:47:25 | 01:00:00 | 8 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **14** | - -Relevant GitHub issues: [#12312](https://github.com/dotnet/core-eng/issues/12312) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:12:40 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20210224.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1010223) | -|:-----:|:-----:| -| Time to Rollout | 00:58:37 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20210224.05](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1009944) | [20210224.15](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1010655) | [20210224.19](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1010856) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 01:37:48 | 00:34:38 | 00:34:59 | -| Critical/blocking issues created | 1 | 0 | 0 | -| Hotfixes | 0 | 1 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021022402](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1010216) | -|:-----:|:-----:| -| Time to Rollout | 01:12:40 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-02-24.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-02-24.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-02-24.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-03.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-03.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-03.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-03.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 03 March 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:41:27 | 01:00:00 | 0 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - -Relevant GitHub issues: [#12400](https://github.com/dotnet/core-eng/issues/12400) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:03:31 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210303.05](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1020759) | -|:-----:|:-----:| -| Time to Rollout | 00:41:27 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021030301](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1021177) | -|:-----:|:-----:| -| Time to Rollout | 01:03:31 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-03.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-03.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-03.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-05.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-05.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-05.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-05.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 05 March 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 05:00:47 | 01:00:00 | 17 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **22** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210304.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1023947) | [20210304.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1024002) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:33:42 | 04:27:05 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-05.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-05.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-05.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-10.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-10.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-10.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-10.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 10 March 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:58:59 | 00:30:00 | 6 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **6** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20210310.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1031943) | [20210310.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1032139) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:56:33 | 01:02:26 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-10.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-10.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-10.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-17.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-17.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-17.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-17.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 17 March 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:00:27 | 01:00:00 | 9 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **14** | - -Relevant GitHub issues: [#12542](https://github.com/dotnet/core-eng/issues/12542) -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:58:15 | 00:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:05:57 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210317.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1043816) | -|:-----:|:-----:| -| Time to Rollout | 03:00:27 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20210317.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1044072) | -|:-----:|:-----:| -| Time to Rollout | 00:58:15 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021031701](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1044069) | -|:-----:|:-----:| -| Time to Rollout | 01:05:57 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-17.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-17.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-17.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-24.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-24.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-24.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-24.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 24 March 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:04:40 | 00:30:00 | 7 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **13** | - -Relevant GitHub issues: [#12630](https://github.com/dotnet/core-eng/issues/12630) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:12:19 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20210324.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1054650) | [20210325.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1056085) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:56:20 | 01:08:20 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021032401](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1054641) | -|:-----:|:-----:| -| Time to Rollout | 01:12:19 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-24.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-24.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-24.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-31.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-31.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-31.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-03-31.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 31 March 2021 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:03:32 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:04:26 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2021033102](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1065964) | -|:-----:|:-----:| -| Time to Rollout | 01:03:32 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20210331.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1065977) | -|:-----:|:-----:| -| Time to Rollout | 01:04:26 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-31.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-31.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-03-31.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-07.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-07.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-07.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-07.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 07 April 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:09:34 | 00:30:00 | 7 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - -Relevant GitHub issues: [#12752](https://github.com/dotnet/core-eng/issues/12752) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:12:53 | 00:30:00 | 7 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **13** | - -Relevant GitHub issues: [#12754](https://github.com/dotnet/core-eng/issues/12754) -# Itemized Scorecard - -## arcade-services - -| Metric | [20210407.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1076410) | [20210407.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1076607) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:13:03 | 00:56:31 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021040702](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1076411) | [2021040703](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1076867) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:10:14 | 01:02:39 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-04-07.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-04-07.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-04-07.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-14.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-14.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-14.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-14.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 14 April 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:51:45 | 00:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:13:19 | 00:30:00 | 7 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - -Relevant GitHub issues: [#12835](https://github.com/dotnet/core-eng/issues/12835) -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 06:23:21 | 01:00:00 | 22 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **22** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20210414.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1088657) | -|:-----:|:-----:| -| Time to Rollout | 00:51:45 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021041403](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1088646) | [2021041501](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1090754) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:10:09 | 01:03:10 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20210414.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1088239) | -|:-----:|:-----:| -| Time to Rollout | 06:23:21 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-04-14.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-04-14.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-04-14.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-21.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-21.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-21.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-21.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 21 April 2021 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:20:27 | 00:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2021042101](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1100182) | -|:-----:|:-----:| -| Time to Rollout | 01:20:27 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-04-21.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-04-21.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-04-21.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-28.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-28.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-28.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-04-28.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 28 April 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 06:40:07 | 01:00:00 | 23 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 4 | 0 | 20 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **43** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:54:52 | 00:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:03:55 | 00:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210428.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1111623) | [20210428.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1112127) | [20210428.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1112257) | [20210428.05](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1112365) | [20210430.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1115651) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 02:01:12 | 00:36:59 | 00:37:26 | 02:37:40 | 00:46:50 | -| Critical/blocking issues created | 0 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 1 | 1 | 1 | 1 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - -## arcade-services - -| Metric | [20210428.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1112105) | -|:-----:|:-----:| -| Time to Rollout | 00:54:52 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021042801](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1112099) | -|:-----:|:-----:| -| Time to Rollout | 01:03:55 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-04-28.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-04-28.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-04-28.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-05.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-05.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-05.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-05.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 05 May 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:54:01 | 01:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210505.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1123317) | -|:-----:|:-----:| -| Time to Rollout | 00:54:01 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-05-05.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-05-05.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-05-05.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-12.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-12.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-12.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-12.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 12 May 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:15:29 | 01:00:00 | 2 | -| Critical/blocking issues created | 2 | 0 | 2 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - -Relevant GitHub issues: [#13042](https://github.com/dotnet/core-eng/issues/13042), [#13055](https://github.com/dotnet/core-eng/issues/13055) -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:51:15 | 00:30:00 | 6 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **6** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:16:57 | 00:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210512.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1134689) | -|:-----:|:-----:| -| Time to Rollout | 01:15:29 | -| Critical/blocking issues created | 2 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20210512.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1135188) | [20210512.4](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1135385) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:59:08 | 00:52:07 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021051201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1135158) | -|:-----:|:-----:| -| Time to Rollout | 01:16:57 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-05-12.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-05-12.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-05-12.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-19.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-19.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-19.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-19.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 19 May 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:21:42 | 01:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:43:11 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20210519.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1146103) | [20210519.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1146341) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:23:07 | 00:58:35 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20210519.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1145454) | -|:-----:|:-----:| -| Time to Rollout | 01:43:11 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-05-19.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-05-19.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-05-19.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-26.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-26.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-26.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-05-26.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 26 May 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:05:50 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:07:19 | 01:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210526.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1157089) | -|:-----:|:-----:| -| Time to Rollout | 01:05:50 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20210526.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1157983) | [20210526.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1158152) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:59:44 | 01:07:35 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-05-26.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-05-26.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-05-26.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-02.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-02.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-02.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-02.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 02 June 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:56:01 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:59:20 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:48:15 | 01:30:00 | 6 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **6** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210602.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1166995) | -|:-----:|:-----:| -| Time to Rollout | 01:56:01 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20210602.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1167574) | -|:-----:|:-----:| -| Time to Rollout | 00:59:20 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021060201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1167563) | [2021060208](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1168350) | [2021060210](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1168442) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 01:27:42 | 00:08:25 | 01:12:08 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-02.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-02.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-02.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-09.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-09.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-09.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-09.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 09 June 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:47:34 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:19:23 | 01:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:10:36 | 01:30:00 | 11 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **17** | - -Relevant GitHub issues: [#13301](https://github.com/dotnet/core-eng/issues/13301) -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210609.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1178846) | -|:-----:|:-----:| -| Time to Rollout | 00:47:34 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20210609.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1179541) | [20210609.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1179697) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:12:57 | 01:06:26 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021060903](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1179526) | [2021060904](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1179776) | [2021061006](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1181777) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 01:46:03 | 01:10:52 | 01:13:41 | -| Critical/blocking issues created | 1 | 0 | 0 | -| Hotfixes | 0 | 0 | 1 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-09.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-09.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-09.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-16.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-16.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-16.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-16.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 16 June 2021 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:19:36 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2021061601](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1190471) | -|:-----:|:-----:| -| Time to Rollout | 01:19:36 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-16.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-16.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-16.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-23.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-23.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-23.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-23.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 23 June 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:15:34 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 2 | 0 | 10 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **10** | - -Relevant GitHub issues: [#13454](https://github.com/dotnet/core-eng/issues/13454), [#13456](https://github.com/dotnet/core-eng/issues/13456) -# Itemized Scorecard - -## arcade-services - -| Metric | [20210623.9](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1202261) | -|:-----:|:-----:| -| Time to Rollout | 01:15:34 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-23.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-23.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-23.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-24.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-24.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-24.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-24.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 24 June 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:39:46 | 02:30:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210624.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1203501) | -|:-----:|:-----:| -| Time to Rollout | 02:39:46 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-24.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-24.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-24.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-25.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-25.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-25.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-25.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 25 June 2021 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:07:35 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2021062502](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1207171) | -|:-----:|:-----:| -| Time to Rollout | 01:07:35 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-25.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-25.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-25.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-30.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-30.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-30.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-06-30.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 30 June 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:09:52 | 01:30:00 | 7 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - -Relevant GitHub issues: [#13570](https://github.com/dotnet/core-eng/issues/13570) -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 07:00:02 | 02:30:00 | 19 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **19** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:28:52 | 01:30:00 | 8 | -| Critical/blocking issues created | 3 | 0 | 3 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **16** | - -Relevant GitHub issues: [#13571](https://github.com/dotnet/core-eng/issues/13571), [#13575](https://github.com/dotnet/core-eng/issues/13575), [#13576](https://github.com/dotnet/core-eng/issues/13576) -# Itemized Scorecard - -## arcade-services - -| Metric | [20210630.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1214146) | [20210630.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1214477) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 02:12:12 | 00:57:40 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20210630.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1213331) | [20210630.05](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1214543) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 06:14:40 | 00:45:22 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021063004](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1214196) | [2021063007](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1214756) | [2021063008](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1214774) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 02:00:53 | 00:19:46 | 01:08:13 | -| Critical/blocking issues created | 3 | 0 | 0 | -| Hotfixes | 0 | 0 | 1 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-30.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-30.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-06-30.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-07.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-07.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-07.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-07.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 07 July 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:14:05 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:10:01 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:33:09 | 01:30:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20210707.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1224724) | -|:-----:|:-----:| -| Time to Rollout | 01:14:05 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20210707.05](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1223933) | -|:-----:|:-----:| -| Time to Rollout | 01:10:01 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021070703](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1224715) | -|:-----:|:-----:| -| Time to Rollout | 01:33:09 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-07.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-07.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-07.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-15.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-15.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-15.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-15.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 15 July 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:54:40 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210715.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1241184) | -|:-----:|:-----:| -| Time to Rollout | 01:54:40 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-15.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-15.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-15.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-21.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-21.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-21.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-21.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 21 July 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:07:18 | 01:30:00 | 11 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **11** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:25:42 | 01:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20210721.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1251243) | [20210721.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1251592) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 03:04:48 | 01:02:30 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021072101](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1251240) | -|:-----:|:-----:| -| Time to Rollout | 02:25:42 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-21.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-21.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-21.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-22.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-22.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-22.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-22.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 22 July 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:22:04 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210722.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1252932) | -|:-----:|:-----:| -| Time to Rollout | 01:22:04 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-22.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-22.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-22.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-28.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-28.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-28.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-07-28.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 28 July 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:09:42 | 01:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:45:21 | 02:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:19:10 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20210728.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1264096) | [20210728.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1264284) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:15:15 | 00:54:27 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20210728.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1263251) | -|:-----:|:-----:| -| Time to Rollout | 02:45:21 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021072801](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1264085) | -|:-----:|:-----:| -| Time to Rollout | 01:19:10 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-28.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-28.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-07-28.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-02.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-02.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-02.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-02.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 02 August 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:41:11 | 02:30:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210802.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1271058) | -|:-----:|:-----:| -| Time to Rollout | 02:41:11 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-02.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-02.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-02.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-04.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-04.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-04.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-04.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 04 August 2021 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:11:54 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:06:40 | 01:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2021080401](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1276246) | -|:-----:|:-----:| -| Time to Rollout | 01:11:54 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20210804.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1276253) | [20210804.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1276453) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:13:49 | 00:52:51 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-04.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-04.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-04.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-10.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-10.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-10.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-10.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 10 August 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:06:14 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **50** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210810.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1285525) | -|:-----:|:-----:| -| Time to Rollout | 02:06:14 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-10.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-10.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-10.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-11.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-11.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-11.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-11.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 11 August 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:08:21 | 01:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:31:58 | 01:30:00 | 5 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **6** | - -Relevant GitHub issues: [#14051](https://github.com/dotnet/core-eng/issues/14051) -# Itemized Scorecard - -## arcade-services - -| Metric | [20210811.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1288264) | [20210811.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1288365) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:03:49 | 01:04:32 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021081101](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1288270) | [2021081102](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1288401) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:15:38 | 01:16:20 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-11.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-11.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-11.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-18.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-18.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-18.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-18.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 18 August 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:13:57 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:48:16 | 01:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:15:51 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210818.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1301450) | -|:-----:|:-----:| -| Time to Rollout | 01:13:57 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20210818.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1302041) | [20210818.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1302225) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:56:44 | 00:51:32 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021081801](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1302032) | -|:-----:|:-----:| -| Time to Rollout | 01:15:51 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-18.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-18.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-18.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-25.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-25.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-25.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-25.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 25 August 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:21:25 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:02:51 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:25:19 | 01:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **9** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210825.07](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1317553) | -|:-----:|:-----:| -| Time to Rollout | 01:21:25 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20210825.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1317851) | -|:-----:|:-----:| -| Time to Rollout | 01:02:51 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021082501](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1317852) | [2021082701](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1324469) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:15:12 | 01:10:07 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-25.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-25.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-25.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-27.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-27.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-27.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-08-27.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 27 August 2021 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:10:07 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2021082701](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1324469) | -|:-----:|:-----:| -| Time to Rollout | 01:10:07 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-27.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-27.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-08-27.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-01.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-01.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-01.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-01.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 01 September 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:53:28 | 01:30:00 | 10 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **10** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:55:27 | 02:30:00 | 10 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **10** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20210901.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1335034) | [20210901.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1335244) | [20210901.4](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1335467) | [20210901.5](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1335703) | -|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 01:06:35 | 01:01:46 | 00:52:12 | 00:52:55 | -| Critical/blocking issues created | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20210901.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1333772) | -|:-----:|:-----:| -| Time to Rollout | 04:55:27 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-01.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-01.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-01.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-02.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-02.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-02.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-02.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 02 September 2021 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:46:20 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2021090201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1338062) | -|:-----:|:-----:| -| Time to Rollout | 00:46:20 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-02.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-02.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-02.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-08.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-08.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-08.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-08.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 08 September 2021 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:49:15 | 01:30:00 | 2 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - -Relevant GitHub issues: [#14316](https://github.com/dotnet/core-eng/issues/14316) -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:02:38 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:34:50 | 02:30:00 | 9 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **10** | - -Relevant GitHub issues: [#14306](https://github.com/dotnet/core-eng/issues/14306) -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2021090801](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1349536) | [2021090904](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1353094) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:59:33 | 00:49:42 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## arcade-services - -| Metric | [20210908.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1349524) | -|:-----:|:-----:| -| Time to Rollout | 01:02:38 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20210908.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1348603) | -|:-----:|:-----:| -| Time to Rollout | 04:34:50 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-08.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-08.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-08.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-14.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-14.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-14.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-14.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 14 September 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:27:15 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210914.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1361338) | [20210915.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1364056) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:47:50 | 00:39:25 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-14.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-14.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-14.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-15.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-15.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-15.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-15.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 15 September 2021 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:56:20 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 06:02:52 | 01:30:00 | 19 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **69** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2021091501](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1364438) | -|:-----:|:-----:| -| Time to Rollout | 00:56:20 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20210915.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1364445) | [20210915.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1364685) | [20210915.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1364764) | [20210915.4](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1364903) | [20210915.5](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1365039) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 01:55:06 | 01:04:16 | 01:08:46 | 00:54:30 | 01:00:14 | -| Critical/blocking issues created | 0 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-15.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-15.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-15.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-22.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-22.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-22.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-22.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 22 September 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:40:39 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:49:03 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:34:29 | 01:30:00 | 5 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **5** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210922.06](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1377995) | -|:-----:|:-----:| -| Time to Rollout | 00:40:39 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021092201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1378474) | -|:-----:|:-----:| -| Time to Rollout | 00:49:03 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20210922.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1378493) | [20210922.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1378662) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:18:33 | 01:15:56 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-22.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-22.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-22.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-29.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-29.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-29.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-09-29.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 29 September 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:08:45 | 02:30:00 | 3 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **9** | - -Relevant GitHub issues: [#14541](https://github.com/dotnet/core-eng/issues/14541) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:34:51 | 01:30:00 | 5 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 1 | 0 | 10 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **66** | - -Relevant GitHub issues: [#14552](https://github.com/dotnet/core-eng/issues/14552) -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:13:16 | 01:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20210929.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1392836) | [20210929.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1393736) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 02:28:44 | 00:40:01 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021092901](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1393285) | [2021093003](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1396217) | [2021093004](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1396293) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 01:03:23 | 00:27:28 | 01:04:00 | -| Critical/blocking issues created | 1 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 1 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - -## arcade-services - -| Metric | [20210929.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1393292) | [20210929.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1393447) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:07:38 | 01:05:38 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-29.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-29.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-09-29.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-06.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-06.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-06.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-06.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 06 October 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:35:15 | 02:30:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:58:34 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:07:35 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20211006.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1405645) | -|:-----:|:-----:| -| Time to Rollout | 02:35:15 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021100601](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1406152) | -|:-----:|:-----:| -| Time to Rollout | 00:58:34 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20211006.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1406160) | -|:-----:|:-----:| -| Time to Rollout | 01:07:35 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-06.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-06.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-06.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-13.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-13.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-13.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-13.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 13 October 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:15:26 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 05:42:11 | 02:30:00 | 13 | -| Critical/blocking issues created | 2 | 0 | 2 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **20** | - -Relevant GitHub issues: [#14638](https://github.com/dotnet/core-eng/issues/14638), [#14645](https://github.com/dotnet/core-eng/issues/14645) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:57:43 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20211013.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1418280) | -|:-----:|:-----:| -| Time to Rollout | 01:15:26 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20211013.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1417701) | [20211013.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1418588) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 03:14:03 | 02:28:08 | -| Critical/blocking issues created | 2 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021101301](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1418287) | -|:-----:|:-----:| -| Time to Rollout | 00:57:43 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-13.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-13.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-13.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-20.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-20.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-20.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-20.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 20 October 2021 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:56:53 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:28:06 | 01:30:00 | 8 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 09:24:40 | 02:30:00 | 28 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **28** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2021102001](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1432554) | -|:-----:|:-----:| -| Time to Rollout | 00:56:53 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20211020.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1432555) | [20211020.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1432715) | [20211020.4](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1432940) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 01:19:54 | 01:06:34 | 01:01:38 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20211020.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1431853) | -|:-----:|:-----:| -| Time to Rollout | 09:24:40 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-20.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-20.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-20.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-23.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-23.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-23.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-23.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 23 October 2021 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:02:22 | 01:30:00 | 3 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - -Relevant GitHub issues: [#14723](https://github.com/dotnet/core-eng/issues/14723) -# Itemized Scorecard - -## arcade-services - -| Metric | [20211023.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1437823) | [20211023.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1437897) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:07:21 | 00:55:01 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-23.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-23.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-23.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-27.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-27.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-27.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-10-27.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 27 October 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:52:00 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:55:02 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:18:56 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20211027.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1442891) | [20211027.07](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1443007) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 00:14:36 | 00:37:24 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021102704](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1443064) | -|:-----:|:-----:| -| Time to Rollout | 00:55:02 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20211027.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1443070) | -|:-----:|:-----:| -| Time to Rollout | 01:18:56 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-27.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-27.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-10-27.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-03.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-03.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-03.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-03.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 03 November 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:44:13 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:00:16 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20211103.10](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1452747) | -|:-----:|:-----:| -| Time to Rollout | 00:44:13 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021110301](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1452860) | -|:-----:|:-----:| -| Time to Rollout | 01:00:16 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-11-03.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-11-03.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-11-03.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-10.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-10.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-10.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-10.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 10 November 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:42:44 | 02:30:00 | 9 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **65** | - -Relevant GitHub issues: [#14936](https://github.com/dotnet/core-eng/issues/14936) -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20211110.09](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1463079) | [20211111.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1465607) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 04:06:11 | 00:36:33 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-11-10.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-11-10.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-11-10.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-17.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-17.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-17.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-17.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 17 November 2021 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:05:58 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:31:33 | 01:30:00 | 5 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **5** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:23:58 | 02:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2021111701](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1474205) | -|:-----:|:-----:| -| Time to Rollout | 01:05:58 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20211117.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1474210) | [20211117.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1474402) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:22:34 | 01:08:59 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20211117.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1474046) | -|:-----:|:-----:| -| Time to Rollout | 03:23:58 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-11-17.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-11-17.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-11-17.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-30.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-30.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-30.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-11-30.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 30 November 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 06:19:06 | 02:30:00 | 16 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **16** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20211130.07](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1490779) | [20211201.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1492348) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:32:46 | 04:46:20 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-11-30.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-11-30.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-11-30.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-12-01.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-12-01.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-12-01.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-12-01.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 01 December 2021 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:45:12 | 01:30:00 | 6 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **6** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:29:53 | 01:30:00 | 8 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2021120101](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1492651) | [2021120102](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1492783) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:20:18 | 01:24:54 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## arcade-services - -| Metric | [20211201.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1492655) | [20211201.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1492801) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:45:41 | 01:44:12 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-12-01.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-12-01.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-12-01.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-12-08.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-12-08.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-12-08.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-12-08.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 08 December 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:47:29 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:17:05 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20211208.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1502877) | -|:-----:|:-----:| -| Time to Rollout | 00:47:29 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021120801](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1503417) | -|:-----:|:-----:| -| Time to Rollout | 01:17:05 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-12-08.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-12-08.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-12-08.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-12-15.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-12-15.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-12-15.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2021-12-15.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 15 December 2021 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:07:15 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:28:07 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:15:23 | 01:30:00 | 12 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **12** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20211215.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1514705) | [20211215.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1514798) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:07:26 | 00:59:49 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2021121501](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1514848) | -|:-----:|:-----:| -| Time to Rollout | 01:28:07 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20211215.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1514843) | [20211215.6](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1515397) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 03:17:24 | 00:57:59 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-12-15.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-12-15.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2021-12-15.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-05.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-05.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-05.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-05.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 05 January 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:08:12 | 02:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:11:37 | 01:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:19:49 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220105.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1536640) | -|:-----:|:-----:| -| Time to Rollout | 03:08:12 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20220105.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1537330) | [20220105.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1537603) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:13:04 | 00:58:33 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022010503](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1537325) | -|:-----:|:-----:| -| Time to Rollout | 01:19:49 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-01-05.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-01-05.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-01-05.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-12.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-12.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-12.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-12.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 12 January 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:00:31 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:02:45 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:18:15 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220112.06](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1548923) | -|:-----:|:-----:| -| Time to Rollout | 01:00:31 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20220112.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1549179) | -|:-----:|:-----:| -| Time to Rollout | 01:02:45 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022011203](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1549169) | -|:-----:|:-----:| -| Time to Rollout | 01:18:15 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-01-12.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-01-12.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-01-12.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-19.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-19.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-19.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-19.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 19 January 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:35:27 | 02:30:00 | 5 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **5** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220119.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1560805) | -|:-----:|:-----:| -| Time to Rollout | 03:35:27 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-01-19.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-01-19.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-01-19.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-26.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-26.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-26.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-01-26.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 26 January 2022 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:00:09 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:35:21 | 01:30:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20220126.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1573971) | -|:-----:|:-----:| -| Time to Rollout | 01:00:09 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022012606](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1573913) | -|:-----:|:-----:| -| Time to Rollout | 01:35:21 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-01-26.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-01-26.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-01-26.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-02.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-02.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-02.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-02.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 02 February 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:35:59 | 02:30:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **6** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:05:27 | 01:30:00 | 0 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - -Relevant GitHub issues: [#15513](https://github.com/dotnet/core-eng/issues/15513) -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:15:46 | 01:30:00 | 8 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220202.07](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1586982) | [20220202.13](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1587216) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:54:25 | 00:41:34 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022020201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1587450) | -|:-----:|:-----:| -| Time to Rollout | 01:05:27 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20220202.4](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1587515) | [20220202.5](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1587750) | [20220202.6](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1588390) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 01:09:42 | 01:08:01 | 00:58:03 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-02-02.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-02-02.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-02-02.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-09.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-09.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-09.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-09.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 09 February 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:12:19 | 02:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **8** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:36:58 | 01:30:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:19:37 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220209.05](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1602573) | [20220210.06](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1605603) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 02:23:54 | 00:48:25 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022020903](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1602892) | -|:-----:|:-----:| -| Time to Rollout | 01:36:58 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20220209.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1602930) | -|:-----:|:-----:| -| Time to Rollout | 01:19:37 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-02-09.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-02-09.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-02-09.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-17.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-17.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-17.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-17.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 17 February 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 07:58:02 | 02:30:00 | 22 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **22** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:25:56 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220217.05](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1618749) | -|:-----:|:-----:| -| Time to Rollout | 07:58:02 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022021702](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1619040) | -|:-----:|:-----:| -| Time to Rollout | 01:25:56 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-02-17.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-02-17.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-02-17.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-21.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-21.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-21.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-02-21.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 21 February 2022 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:13:12 | 01:30:00 | 11 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **61** | - -Relevant GitHub issues: [#15683](https://github.com/dotnet/core-eng/issues/15683) -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 08:34:38 | 02:30:00 | 25 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **26** | - -Relevant GitHub issues: [#15685](https://github.com/dotnet/core-eng/issues/15685) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:14:07 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20220223.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1628625) | [20220223.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1628894) | [20220223.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1629220) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 01:31:30 | 01:40:53 | 01:00:49 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20220223.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1627715) | [20220223.12](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1629376) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 07:50:29 | 00:44:09 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022022303](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1628591) | -|:-----:|:-----:| -| Time to Rollout | 01:14:07 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-02-21.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-02-21.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-02-21.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-02.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-02.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-02.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-02.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 02 March 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:37:16 | 02:30:00 | 5 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **5** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:25:19 | 01:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220302.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1640693) | -|:-----:|:-----:| -| Time to Rollout | 03:37:16 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20220302.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1640951) | [20220302.4](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1641201) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:27:31 | 00:57:48 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-02.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-02.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-02.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-09.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-09.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-09.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-09.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 09 March 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:39:20 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:13:32 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:47:44 | 01:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220309.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1653949) | -|:-----:|:-----:| -| Time to Rollout | 01:39:20 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20220309.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1654309) | -|:-----:|:-----:| -| Time to Rollout | 01:13:32 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022030901](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1654274) | -|:-----:|:-----:| -| Time to Rollout | 01:47:44 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-09.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-09.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-09.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-16.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-16.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-16.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-16.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 16 March 2022 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:02:03 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:55:23 | 01:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 05:54:00 | 02:30:00 | 14 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **14** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20220316.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1666756) | -|:-----:|:-----:| -| Time to Rollout | 01:02:03 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022031605](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1666722) | -|:-----:|:-----:| -| Time to Rollout | 01:55:23 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20220316.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1666606) | -|:-----:|:-----:| -| Time to Rollout | 05:54:00 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-16.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-16.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-16.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-23.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-23.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-23.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-23.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 23 March 2022 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 00:55:07 | 01:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:38:10 | 01:30:00 | 1 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20220323.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1678295) | -|:-----:|:-----:| -| Time to Rollout | 00:55:07 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022032303](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1678246) | -|:-----:|:-----:| -| Time to Rollout | 01:38:10 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-23.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-23.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-23.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-30.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-30.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-30.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-03-30.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 30 March 2022 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:57:40 | 01:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 09:45:31 | 02:30:00 | 30 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **30** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2022033002](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1690777) | -|:-----:|:-----:| -| Time to Rollout | 01:57:40 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20220330.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1690482) | -|:-----:|:-----:| -| Time to Rollout | 09:45:31 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-30.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-30.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-03-30.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-06.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-06.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-06.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-06.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 06 April 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:15:29 | 02:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:02:04 | 01:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:26:37 | 01:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220406.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1702546) | -|:-----:|:-----:| -| Time to Rollout | 03:15:29 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022040604](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1703243) | -|:-----:|:-----:| -| Time to Rollout | 02:02:04 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20220406.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1703265) | -|:-----:|:-----:| -| Time to Rollout | 02:26:37 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-04-06.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-04-06.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-04-06.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-13.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-13.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-13.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-13.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 13 April 2022 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:55:10 | 01:30:00 | 2 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **2** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:09:17 | 01:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2022041301](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1715704) | -|:-----:|:-----:| -| Time to Rollout | 01:55:10 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20220413.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1715734) | -|:-----:|:-----:| -| Time to Rollout | 02:09:17 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-04-13.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-04-13.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-04-13.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-20.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-20.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-20.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-20.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 20 April 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:49:48 | 02:30:00 | 10 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 1 | 0 | 10 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **70** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:01:59 | 01:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:31:45 | 01:30:00 | 5 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **5** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220420.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1728386) | [20220420.11](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1728871) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 03:01:58 | 01:47:50 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 1 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022042003](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1728588) | -|:-----:|:-----:| -| Time to Rollout | 02:01:59 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20220420.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1728604) | -|:-----:|:-----:| -| Time to Rollout | 02:31:45 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-04-20.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-04-20.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-04-20.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-27.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-27.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-27.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-04-27.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 27 April 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 05:52:20 | 02:30:00 | 14 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **15** | - -Relevant GitHub issues: [#9203](https://github.com/dotnet/arcade/issues/9203) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:00:42 | 01:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220427.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1740249) | [20220428.07](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1742680) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 04:16:59 | 01:35:21 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022042701](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1740896) | -|:-----:|:-----:| -| Time to Rollout | 02:00:42 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-04-27.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-04-27.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-04-27.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-03.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-03.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-03.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-03.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 03 May 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:27:51 | 02:30:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220503.09](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1749980) | -|:-----:|:-----:| -| Time to Rollout | 01:27:51 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-05-03.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-05-03.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-05-03.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-04.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-04.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-04.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-04.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 04 May 2022 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:03:20 | 01:30:00 | 3 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **3** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2022050401](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1752133) | -|:-----:|:-----:| -| Time to Rollout | 02:03:20 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-05-04.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-05-04.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-05-04.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-11.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-11.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-11.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-11.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 11 May 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:55:07 | 02:30:00 | 6 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **6** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220511.07](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1764880) | -|:-----:|:-----:| -| Time to Rollout | 03:55:07 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-05-11.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-05-11.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-05-11.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-19.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-19.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-19.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-05-19.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 19 May 2022 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:24:22 | 01:30:00 | 4 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **4** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2022051910](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1779998) | -|:-----:|:-----:| -| Time to Rollout | 02:24:22 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-05-19.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-05-19.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-05-19.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-07-13.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-07-13.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-07-13.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-07-13.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 13 July 2022 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:27:28 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:12:35 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20220713.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1878294) | -|:-----:|:-----:| -| Time to Rollout | 02:27:28 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022071302](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1878286) | -|:-----:|:-----:| -| Time to Rollout | 02:12:35 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-07-13.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-07-13.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-07-13.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-07-20.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-07-20.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-07-20.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-07-20.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 20 July 2022 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:11:56 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:11:45 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:11:18 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20220720.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1893240) | -|:-----:|:-----:| -| Time to Rollout | 02:11:56 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20220720.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1892290) | -|:-----:|:-----:| -| Time to Rollout | 04:11:45 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022072007](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1893218) | -|:-----:|:-----:| -| Time to Rollout | 02:11:18 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-07-20.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-07-20.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-07-20.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-07-27.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-07-27.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-07-27.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-07-27.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 27 July 2022 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:07:07 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 10:09:44 | 06:00:00 | 50 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **50** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:08:36 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20220727.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1906963) | -|:-----:|:-----:| -| Time to Rollout | 02:07:07 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20220727.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1906407) | [20220727.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1906860) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:02:18 | 09:07:26 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022072701](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1906949) | -|:-----:|:-----:| -| Time to Rollout | 02:08:36 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-07-27.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-07-27.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-07-27.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-03.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-03.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-03.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-03.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 03 August 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:24:49 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:08:53 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220803.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1920846) | -|:-----:|:-----:| -| Time to Rollout | 02:24:49 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022080302](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1920986) | -|:-----:|:-----:| -| Time to Rollout | 02:08:53 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-03.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-03.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-03.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-10.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-10.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-10.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-10.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 10 August 2022 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:04:56 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:34:24 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:54:41 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **5** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20220810.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1934660) | -|:-----:|:-----:| -| Time to Rollout | 02:04:56 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20220810.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1933942) | -|:-----:|:-----:| -| Time to Rollout | 01:34:24 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022081004](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1934856) | [2022081009](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1935400) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:58:18 | 01:56:23 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-10.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-10.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-10.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-17.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-17.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-17.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-17.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 17 August 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:10:02 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:56:37 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:08:32 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220817.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1948642) | -|:-----:|:-----:| -| Time to Rollout | 04:10:02 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022081705](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1949498) | -|:-----:|:-----:| -| Time to Rollout | 01:56:37 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20220817.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1949514) | -|:-----:|:-----:| -| Time to Rollout | 02:08:32 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-17.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-17.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-17.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-24.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-24.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-24.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-24.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 24 August 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:50:04 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:01:09 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:05:30 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220824.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1964104) | -|:-----:|:-----:| -| Time to Rollout | 02:50:04 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022082402](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1964342) | -|:-----:|:-----:| -| Time to Rollout | 02:01:09 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20220824.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1964379) | -|:-----:|:-----:| -| Time to Rollout | 02:05:30 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-24.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-24.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-24.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-31.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-31.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-31.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-08-31.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 31 August 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:15:03 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:19:46 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:22:14 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220831.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1979108) | -|:-----:|:-----:| -| Time to Rollout | 02:15:03 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022083103](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1979578) | -|:-----:|:-----:| -| Time to Rollout | 02:19:46 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20220831.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1979594) | -|:-----:|:-----:| -| Time to Rollout | 02:22:14 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-31.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-31.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-08-31.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-07.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-07.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-07.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-07.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 07 September 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 20:33:40 | 06:00:00 | 50 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **55** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 16:43:32 | 06:00:00 | 50 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 2 | 0 | 10 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **61** | - -Relevant GitHub issues: [#11050](https://github.com/dotnet/arcade/issues/11050) -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 15:34:48 | 06:00:00 | 50 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **56** | - -Relevant GitHub issues: [#11152](https://github.com/dotnet/arcade/issues/11152) -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220907.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1985944) | [20220914.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1992938) | [20220921.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2000111) | [20220928.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006236) | [20220928.07](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006538) | [20221005.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012304) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 01:49:30 | 02:22:12 | 03:12:37 | 04:28:33 | 03:03:21 | 05:37:27 | -| Critical/blocking issues created | 0 | 0 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | 0 | 1 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - -## arcade-services - -| Metric | [20220907.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1986450) | [20220914.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1993059) | [20220921.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2000181) | [20220928.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006329) | [20220928.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006775) | [20220929.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2007780) | [20221005.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012398) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 03:05:58 | 02:05:59 | 02:39:10 | 02:06:25 | 02:08:00 | 02:06:53 | 02:31:07 | -| Critical/blocking issues created | 1 | 0 | 0 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | 0 | 1 | 1 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022090703](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1986433) | [2022090704](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1986673) | [2022090706](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1986730) | [2022091401](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1992921) | [2022092102](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2000178) | [2022092806](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006324) | [2022100501](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012399) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 03:38:54 | 00:24:08 | 02:03:21 | 02:22:52 | 02:25:24 | 02:22:53 | 02:17:16 | -| Critical/blocking issues created | 1 | 0 | 0 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-07.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-07.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-07.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-14.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-14.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-14.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-14.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 14 September 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 22:28:42 | 06:00:00 | 50 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **55** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 11:39:04 | 06:00:00 | 50 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **51** | - -Relevant GitHub issues: [#11152](https://github.com/dotnet/arcade/issues/11152) -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 16:15:10 | 06:00:00 | 50 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 2 | 0 | 10 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **61** | - -Relevant GitHub issues: [#11050](https://github.com/dotnet/arcade/issues/11050) -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220914.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1992938) | [20220921.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2000111) | [20220928.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006236) | [20220928.07](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006538) | [20221005.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012304) | [20221012.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018261) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 02:22:12 | 03:12:37 | 04:28:33 | 03:03:21 | 05:37:27 | 03:44:32 | -| Critical/blocking issues created | 0 | 0 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | 1 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022091401](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1992921) | [2022092102](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2000178) | [2022092806](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006324) | [2022100501](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012399) | [2022101201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018719) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 02:22:52 | 02:25:24 | 02:22:53 | 02:17:16 | 02:10:39 | -| Critical/blocking issues created | 1 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - -## arcade-services - -| Metric | [20220914.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=1993059) | [20220921.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2000181) | [20220928.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006329) | [20220928.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006775) | [20220929.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2007780) | [20221005.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012398) | [20221012.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018725) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 02:05:59 | 02:39:10 | 02:06:25 | 02:08:00 | 02:06:53 | 02:31:07 | 02:37:36 | -| Critical/blocking issues created | 1 | 0 | 0 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | 1 | 1 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-14.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-14.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-14.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-21.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-21.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-21.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-21.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 21 September 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 20:06:30 | 06:00:00 | 50 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **55** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 09:16:12 | 06:00:00 | 50 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **51** | - -Relevant GitHub issues: [#11152](https://github.com/dotnet/arcade/issues/11152) -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 14:09:11 | 06:00:00 | 50 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 2 | 0 | 10 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **61** | - -Relevant GitHub issues: [#11050](https://github.com/dotnet/arcade/issues/11050) -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20220921.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2000111) | [20220928.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006236) | [20220928.07](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006538) | [20221005.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012304) | [20221012.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018261) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 03:12:37 | 04:28:33 | 03:03:21 | 05:37:27 | 03:44:32 | -| Critical/blocking issues created | 0 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 1 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022092102](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2000178) | [2022092806](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006324) | [2022100501](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012399) | [2022101201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018719) | -|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 02:25:24 | 02:22:53 | 02:17:16 | 02:10:39 | -| Critical/blocking issues created | 1 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - -## arcade-services - -| Metric | [20220921.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2000181) | [20220928.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006329) | [20220928.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006775) | [20220929.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2007780) | [20221005.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012398) | [20221012.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018725) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 02:39:10 | 02:06:25 | 02:08:00 | 02:06:53 | 02:31:07 | 02:37:36 | -| Critical/blocking issues created | 1 | 0 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 1 | 1 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-21.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-21.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-21.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-28.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-28.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-28.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-28.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# 28 September 2022 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 06:50:48 | 06:00:00 | 50 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **51** | - -Relevant GitHub issues: [#11152](https://github.com/dotnet/arcade/issues/11152) -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 11:30:01 | 06:00:00 | 50 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 2 | 0 | 10 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **61** | - -Relevant GitHub issues: [#11050](https://github.com/dotnet/arcade/issues/11050) -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 16:53:53 | 06:00:00 | 50 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **55** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2022092806](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006324) | [2022100501](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012399) | [2022101201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018719) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 02:22:53 | 02:17:16 | 02:10:39 | -| Critical/blocking issues created | 1 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - -## arcade-services - -| Metric | [20220928.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006329) | [20220928.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006775) | [20220929.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2007780) | [20221005.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012398) | [20221012.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018725) | -|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 02:06:25 | 02:08:00 | 02:06:53 | 02:31:07 | 02:37:36 | -| Critical/blocking issues created | 1 | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 1 | 1 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20220928.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006236) | [20220928.07](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2006538) | [20221005.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012304) | [20221012.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018261) | -|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 04:28:33 | 03:03:21 | 05:37:27 | 03:44:32 | -| Critical/blocking issues created | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 1 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-28.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-28.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-28.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-30.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-30.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-30.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-09-30.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -# 30 September 2022 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 07:15:36 | 06:00:00 | 50 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **50** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20220929.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2007780) | [20221005.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012398) | [20221012.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018725) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 02:06:53 | 02:31:07 | 02:37:36 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-30.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-30.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-09-30.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-05.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-05.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-05.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-05.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# 05 October 2022 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:27:55 | 06:00:00 | 0 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - -Relevant GitHub issues: [#11152](https://github.com/dotnet/arcade/issues/11152) -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 09:21:59 | 06:00:00 | 50 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **50** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2022100501](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012399) | [2022101201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018719) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 02:17:16 | 02:10:39 | -| Critical/blocking issues created | 1 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20221005.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2012304) | [20221012.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018261) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 05:37:27 | 03:44:32 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-10-05.md)](https://helix.dot.net/f/p/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-10-05.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CTeamProcess%5CRollout-Scorecards%5CScorecard_2022-10-05.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-12.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-12.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-12.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-12.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,75 +0,0 @@ -# 12 October 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:44:32 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:10:39 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:37:36 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20221012.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018261) | -|:-----:|:-----:| -| Time to Rollout | 03:44:32 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022101201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018719) | -|:-----:|:-----:| -| Time to Rollout | 02:10:39 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20221012.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2018725) | -|:-----:|:-----:| -| Time to Rollout | 02:37:36 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-19.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-19.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-19.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-19.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,75 +0,0 @@ -# 19 October 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:34:53 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:15:57 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 05:23:07 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20221019.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2024922) | -|:-----:|:-----:| -| Time to Rollout | 02:34:53 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20221019.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2025037) | -|:-----:|:-----:| -| Time to Rollout | 02:15:57 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022101901](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2025034) | -|:-----:|:-----:| -| Time to Rollout | 05:23:07 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-26.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-26.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-26.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-10-26.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,75 +0,0 @@ -# 26 October 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 07:33:24 | 06:00:00 | 50 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 2 | 0 | 10 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **60** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:11:27 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:14:53 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20221026.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2030098) | [20221026.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2030259) | [20221026.08](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2030349) | -|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 03:01:20 | 01:33:40 | 02:58:24 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 1 | 1 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | - - -## arcade-services - -| Metric | [20221026.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2030210) | -|:-----:|:-----:| -| Time to Rollout | 02:11:27 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022102604](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2030200) | -|:-----:|:-----:| -| Time to Rollout | 02:14:53 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-02.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-02.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-02.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-02.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# 02 November 2022 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:05:15 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:52:46 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **5** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20221102.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2035397) | -|:-----:|:-----:| -| Time to Rollout | 02:05:15 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20221102.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2035302) | [20221102.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2035844) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 03:21:34 | 01:31:12 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-09.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-09.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-09.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-09.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# 09 November 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:41:31 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **5** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:30:04 | 06:00:00 | 0 | -| Critical/blocking issues created | 1 | 0 | 1 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **1** | - -Relevant GitHub issues: [#11575](https://github.com/dotnet/arcade/issues/11575) -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20221109.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2041109) | [20221110.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2042090) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 01:34:43 | 03:06:48 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## arcade-services - -| Metric | [20221109.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2041249) | -|:-----:|:-----:| -| Time to Rollout | 03:30:04 | -| Critical/blocking issues created | 1 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-16.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-16.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-16.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-16.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,75 +0,0 @@ -# 16 November 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:42:08 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:46:29 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:34:26 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20221116.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2046696) | -|:-----:|:-----:| -| Time to Rollout | 02:42:08 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20221116.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2046774) | -|:-----:|:-----:| -| Time to Rollout | 02:46:29 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022111601](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2046758) | -|:-----:|:-----:| -| Time to Rollout | 03:34:26 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-30.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-30.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-30.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-11-30.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# 30 November 2022 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:22:59 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 07:16:53 | 06:00:00 | 50 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **50** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20221130.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2057016) | -|:-----:|:-----:| -| Time to Rollout | 02:22:59 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20221130.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2056853) | [20221130.04](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2057045) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 02:45:06 | 04:31:47 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-12-01.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-12-01.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-12-01.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-12-01.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -# 01 December 2022 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:01:07 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2022120101](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2058132) | -|:-----:|:-----:| -| Time to Rollout | 02:01:07 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-12-07.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-12-07.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-12-07.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2022-12-07.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,75 +0,0 @@ -# 07 December 2022 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:30:49 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **5** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:02:22 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:22:07 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20221206.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2061925) | [20221207.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2062553) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 02:09:11 | 01:21:38 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2022120604](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2061926) | -|:-----:|:-----:| -| Time to Rollout | 02:02:22 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20221207.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2062444) | -|:-----:|:-----:| -| Time to Rollout | 03:22:07 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-01-11.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-01-11.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-01-11.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-01-11.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -# 11 January 2023 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:09:26 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2023011101](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2085677) | -|:-----:|:-----:| -| Time to Rollout | 02:09:26 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-08.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-08.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-08.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-08.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -# 8 March 2023 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:56:56 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:48:02 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-15.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-15.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-15.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-15.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# 15 March 2023 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:54:51 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 07:46:07 | 06:00:00 | 50 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 1 | 0 | 5 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **55** | - - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2023031404](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2135416) | -|:-----:|:-----:| -| Time to Rollout | 01:54:51 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - -## dotnet-helix-machines - -| Metric | [20230314.03](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2135413) | [20230320.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2139552) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 02:01:24 | 05:44:43 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 1 | -| Rollbacks | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-22.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-22.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-22.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-22.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -# 22 March 2023 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:12:55 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20230322.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2141509) | -|:-----:|:-----:| -| Time to Rollout | 02:12:55 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-29.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-29.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-29.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-03-29.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# 29 March 2023 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:13:25 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:10:16 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2023032901](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2146758) | -|:-----:|:-----:| -| Time to Rollout | 02:13:25 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20230329.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2146770) | -|:-----:|:-----:| -| Time to Rollout | 02:10:16 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-05.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-05.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-05.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-05.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# 05 April 2023 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:58:00 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:56:19 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2023040501](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2151267) | -|:-----:|:-----:| -| Time to Rollout | 01:58:00 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20230405.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2151271) | -|:-----:|:-----:| -| Time to Rollout | 03:56:19 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-12.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-12.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-12.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-12.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# 12 April 2023 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:57:49 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:20:39 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2023041201](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2156637) | -|:-----:|:-----:| -| Time to Rollout | 01:57:49 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20230412.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2156644) | -|:-----:|:-----:| -| Time to Rollout | 02:20:39 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-19.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-19.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-19.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-19.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,52 +0,0 @@ -# 19 April 2023 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:55:17 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:20:20 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - - -## dotnet-helix-service - -| Metric | [2023041901](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2162352) | -|:-----:|:-----:| -| Time to Rollout | 01:55:17 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## arcade-services - -| Metric | [20230419.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2162354) | -|:-----:|:-----:| -| Time to Rollout | 02:20:20 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-27.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-27.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-27.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-04-27.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -# 27 April 2023 Rollout Summaries - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 10:37:47 | 06:00:00 | 50 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **50** | - - -# Itemized Scorecard - -## dotnet-helix-machines - -| Metric | [20230427.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2168756) | -|:-----:|:-----:| -| Time to Rollout | 10:37:47 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-05-03.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-05-03.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-05-03.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-05-03.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,75 +0,0 @@ -# 03 May 2023 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 02:03:56 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 03:59:25 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 1 | 0 | 10 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | TRUE | FALSE | 50 | -| Total | | | **60** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:21:50 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20230503.3](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2172754) | -|:-----:|:-----:| -| Time to Rollout | 02:03:56 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-service - -| Metric | [2023050301](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2172779) | [2023050302](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2173059) | -|:-----:|:-----:|:-----:| -| Time to Rollout | 02:03:52 | 01:55:33 | -| Critical/blocking issues created | 0 | 0 | -| Hotfixes | 0 | 0 | -| Rollbacks | 0 | 1 | -| Service downtime | 00:00:00 | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20230503.02](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2172777) | -|:-----:|:-----:| -| Time to Rollout | 04:21:50 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-05-10.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-05-10.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-05-10.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-05-10.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -# 10 May 2023 Rollout Summaries - -## arcade-services - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 1.01:34:17 | 06:00:00 | 50 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **50** | - - -# Itemized Scorecard - -## arcade-services - -| Metric | [20230510.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2177776) | [20230510.2](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2177932) | [20230510.4](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2178116) | [20230517.1](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2182427) | -|:-----:|:-----:|:-----:|:-----:|:-----:| -| Time to Rollout | 01:44:04 | 03:50:21 | 17:52:50 | 02:07:02 | -| Critical/blocking issues created | 0 | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 00:00:00 | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-05-17.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-05-17.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-05-17.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/TeamProcess/Rollout-Scorecards/Scorecard_2023-05-17.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# 17 May 2023 Rollout Summaries - -## dotnet-helix-service - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 01:55:14 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -## dotnet-helix-machines - -| Metric | Value | Target | Score | -|:--------------------------------:|:--------:|:--------:|:---------:| -| Time to Rollout | 04:12:56 | 06:00:00 | 0 | -| Critical/blocking issues created | 0 | 0 | 0 | -| Hotfixes | 0 | 0 | 0 | -| Rollbacks | 0 | 0 | 0 | -| Service downtime | 00:00:00 | 00:00:00 | 0 | -| Failed to rollout | FALSE | FALSE | 0 | -| Total | | | **0** | - - -# Itemized Scorecard - -## dotnet-helix-service - -| Metric | [2023051701](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2182422) | -|:-----:|:-----:| -| Time to Rollout | 01:55:14 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - - -## dotnet-helix-machines - -| Metric | [20230517.01](https://dev.azure.com/dnceng/7ea9116e-9fac-403d-b258-b31fcf1bb293/_build/results?buildId=2182188) | -|:-----:|:-----:| -| Time to Rollout | 04:12:56 | -| Critical/blocking issues created | 0 | -| Hotfixes | 0 | -| Rollbacks | 0 | -| Service downtime | 00:00:00 | - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/DeploymentProcess.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/DeploymentProcess.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/DeploymentProcess.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/DeploymentProcess.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# Deployment Process Overview - -Business Requirement: The process for deploying our services use a standard model across all services. - -## Consistency in deployment - -This epic will define a standard model that we will follow in order to deploy our services. All deployments follow yaml-based stages in pipelines. Ideally all deployments will use the following pattern: - -**Build Pipeline:** - -1. Build - - Build - - Unit/Functional Tests - - Publish Build Artifacts -2. Validate - - SDL Runs - - Pre-merge checks - -**Deployment Pipeline:** - -For each of service to be deployed: -- Build Pipeline as a resource: - Consume a build pipeline as a resource into a deployment pipeline. This way deployments can be kicked-off independent to builds and re-running of just deployments using the same build artifacts is possible as well. - ``` - resources: - pipelines: - - pipeline: MyAppCI - source: CIPipeline - - pipeline: AnotherCI - source: anotherCIPipeline - ``` -- Download all relevant build artifacts from the attached build pipeline. - -The following are the stages in the deployment pipeline and each depends on the previous one to be completed successfully: -1. Pre-deployment checks -2. Deploy service -3. Post-deployment scenario tests - -**Production Environments** - -Specific to deploying to prod environment is an additional [manual approval check](https://docs.microsoft.com/en-us/azure/devops/pipelines/process/approvals?view=azure-devops#approvals) to prevent accidental deployment. - -**Deployment Workflow** - -![Deployment Process](Images/Deployment_Workflow.svg) - -**Why Stages?** - -Stages provide the flexibility of having logical boundaries in pipelines, and can be arranged into a dependency graph (e.g Run Stage B only if Stage A succeeds). Stages also provide the ability to re-run parts of a pipeline; e.g. rerun a failed deployment or run just parts of a deployment which does not require rerunning the entire pipeline. - -![Stages](Images/Stages.PNG) - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CValidation%5CDeploymentProcess.md)](https://helix.dot.net/f/p/5?p=Documentation%5CValidation%5CDeploymentProcess.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CValidation%5CDeploymentProcess.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/HealthMonitoring.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/HealthMonitoring.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/HealthMonitoring.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/HealthMonitoring.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -# Health Monitoring - -## Features and Processes in Scope - -Helix Services -- Helix Client -- Helix API -- Controller -- EventHub -- ServiceBus -- Data Migration Services - -Arcade-Services: - -- Maestro++ -- Darc API -- BAR -- Telemetry Service -- BARViz - -## Links to Relevant Pipelines and Builds - -MSENG: - -- [Helix-PR-Master](https://dev.azure.com/mseng/Tools/_pipeline/analytics/stageawareoutcome?definitionId=6216&contextType=build) -- [Helix-CI](https://dev.azure.com/mseng/Tools/_build?definitionId=6171&_a=summary&view=ms.vss-pipelineanalytics-web.new-build-definition-pipeline-analytics-view-cardmetrics) -- [Helix-Daily](https://dev.azure.com/mseng/Tools/_build?definitionId=6843&_a=summary&view=ms.vss-pipelineanalytics-web.new-build-definition-pipeline-analytics-view-cardmetrics) -- [Helix Agents - CI](https://dev.azure.com/mseng/Tools/_build?definitionId=6707&_a=summary&view=ms.vss-pipelineanalytics-web.new-build-definition-pipeline-analytics-view-cardmetrics) - -DNCENG (internal): - -- [Arcade-ci](https://dev.azure.com/dnceng/public/_build?definitionId=208&view=ms.vss-pipelineanalytics-web.new-build-definition-pipeline-analytics-view-cardmetrics) -- [Arcade-extensions-ci](https://dev.azure.com/dnceng/public/_build?definitionId=386&view=ms.vss-pipelineanalytics-web.new-build-definition-pipeline-analytics-view-cardmetrics) -- [Arcade-minimalci-sample-ci](https://dev.azure.com/dnceng/public/_build?definitionId=209&view=ms.vss-pipelineanalytics-web.new-build-definition-pipeline-analytics-view-cardmetrics) -- [Arcade-pool-provider-ci](https://dev.azure.com/dnceng/public/_build?definitionId=411&view=ms.vss-pipelineanalytics-web.new-build-definition-pipeline-analytics-view-cardmetrics) -- [Dotnet-arcade-service](https://dev.azure.com/dnceng/public/_build?definitionId=247&view=ms.vss-pipelineanalytics-web.new-build-definition-pipeline-analytics-view-cardmetrics) -- [Arcade-validation-ci](https://dev.azure.com/dnceng/public/_build?definitionId=269&view=ms.vss-pipelineanalytics-web.new-build-definition-pipeline-analytics-view-cardmetrics) -- Helix-machines - - [Build-and-deploy-production](https://dev.azure.com/dnceng/internal/_build?definitionId=145&view=ms.vss-pipelineanalytics-web.new-build-definition-pipeline-analytics-view-cardmetrics) - - [Build-and-deploy-staging](https://dev.azure.com/dnceng/internal/_build?definitionId=103&view=ms.vss-pipelineanalytics-web.new-build-definition-pipeline-analytics-view-cardmetrics) - - [Pr](https://dev.azure.com/dnceng/internal/_build?definitionId=3&view=ms.vss-pipelineanalytics-web.new-build-definition-pipeline-analytics-view-cardmetrics) - - [Pr-prod-queues](https://dev.azure.com/dnceng/internal/_build?definitionId=129&view=ms.vss-pipelineanalytics-web.new-build-definition-pipeline-analytics-view-cardmetrics) - -## Existing Functionality and Processes -[Availability and reliability monitoring](https://msit.powerbi.com/groups/de8c4cb8-b06d-4af8-8609-3182bb4bdc7c/reports/09be7698-941b-4df7-966e-d0e1ca96d656/ReportSection) for PROD services already exists in Power BI from data in AppInsights and Kusto. Our focus for Health Reporting will be based on tests and builds in AzDO. - -## Assumptions -All the builds and deployments we care about are either in mseng or dnceng in AzDO - -## Concerns -- There may be some "weirdness" with AzDO collecting test results when deploying Helix. -- SSL validation pre and post deployment enough or should we do periodic health checking? -- How to health report for Docker and OnPrem? - -## Use Cases and Solutions -- Build status widgets in AzDO Dashboard using data pulled from Pipelines -- Test Run status widgets in AzDO Dashboard using data pulled from Pipelines -- Deployment status widgets in AzDO Dashboard using data pulled from Pipelines - -The above should give the picture needed to know if staging is stable to rollout out to Prod. Health Monitoring of already deployed services in PROD is available in PowerBI and is not covered part of this epic. -Checks to ensure Service Fabric is reachable, up and running, will not fall over with deployment etc., are validated via tests that will get hooked up to pipelines. - -Sample AzDO Dashboard - https://dev.azure.com/dnceng/internal/_dashboards/dashboard/755b52e7-b7a3-423b-bb60-7a01ff7241b8 - -## Dependencies -- AzDO (Dashboard, widgets, pipelines) -- Azure - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CValidation%5CHealthMonitoring.md)](https://helix.dot.net/f/p/5?p=Documentation%5CValidation%5CHealthMonitoring.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CValidation%5CHealthMonitoring.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/HelixValidation.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/HelixValidation.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/HelixValidation.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/HelixValidation.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,33 +0,0 @@ -# Validation for Helix API and Services - -## Pipelines and Jobs -The following are the pipelines and jobs currently running validation, and an example of the kinds of validation that runs during the course of the pipeline. To determine which pipeline your tests should run, compare to what is currently available and use your judgement about where your test would best fit in. -- **PRs** :
    - [Build -> Validate](https://dnceng.visualstudio.com/internal/_build?definitionId=620&_a=summary) - - *Unit and Functional Tests*: Any test that follows the [validation pattern](../Validation/ValidationProcess.md#unit-testing) for unit tests and is not categorized as "PostDeployment", "PreDeployment", or "Nightly", will run during this stage. - - *Code Coverage*: The tests that run above will be used to determine how much code is currently being covered by tests. The Code Coverage tab can be viewed after the completion of the job in Azure DevOps. - -- **CI / Staging**:
    - [Build -> Validate](https://dnceng.visualstudio.com/internal/_build?definitionId=620&_a=summary) -> [Pre-Deploy -> Deploy -> Post-Deploy](https://dnceng.visualstudio.com/internal/_build?definitionId=696&_a=summary) - - *Pre-Deployment Validation*: Powershell scripts. Types of [pre-deployment validation](https://github.com/dotnet/core-eng/blob/main/Documentation/Validation/ValidationProcess.md#pre-deployment) - - Validate HMS Deployment - - Validate Service Fabric Applications - - Validate Resource Groups and Storage Accounts - - *Post-Deployment Validation*: In **Helix.Test.Staging.PostDeployment** test project. Types of [post-deployment validation](https://github.com/dotnet/core-eng/blob/main/Documentation/Validation/ValidationProcess.md#post-deployment) - - Helix API Tests - -- **Production**:
    - [Build -> Validate](https://dnceng.visualstudio.com/internal/_build?definitionId=620&_a=summary) -> [Pre-Deploy -> Deploy](https://dnceng.visualstudio.com/internal/_build?definitionId=697&_a=summary) - - No Post-Deployment Validation as they would be queued behind customer jobs. - -- **[Nightly](https://dev.azure.com/dnceng/internal/_build?definitionId=622&_a=summary)**: - - SDL/Bin Skim - - **Helix.Test.Staging.Nightly** project: Types of [nightly validation](https://github.com/dotnet/core-eng/blob/main/Documentation/Validation/ValidationProcess.md#nightly) - - Sends jobs that echo "hello world" to each queue in staging (currently, only open queues) - -## Where do the Tests live? -Per the [Validation Process](https://github.com/dotnet/core-eng/blob/main/Documentation/Validation/ValidationProcess.md#unit-testing) documentation, tests will live within the solution of the project being tested. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CValidation%5CHelixValidation.md)](https://helix.dot.net/f/p/5?p=Documentation%5CValidation%5CHelixValidation.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CValidation%5CHelixValidation.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/Images/Deployment_Workflow.svg dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/Images/Deployment_Workflow.svg --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/Images/Deployment_Workflow.svg 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/Images/Deployment_Workflow.svg 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ - - -
    Build 
    Build 
    Validate
    Validate
    Pre-Deployment Checks
    Pre-Deployment Checks
    Deploy Serivice 1
    Deploy Serivice 1
    Post-Deployment Checks
    Post-Deployment Checks
    Pre-Deployment Checks
    Pre-Deployment Checks
    Deploy Serivice 2
    Deploy Serivice 2
    Post-Deployment Checks
    Post-Deployment Checks
    Build Pipeline
    Build Pipeline
    Deployment Pipeline
    Deployment Pipeline
    \ No newline at end of file Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/Images/newxunitproject.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/Images/newxunitproject.png differ diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/Images/OSOBValidation.svg dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/Images/OSOBValidation.svg --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/Images/OSOBValidation.svg 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/Images/OSOBValidation.svg 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ - - -
    Build
    Build
    Create VM
    Create <b>VM</b>
    On-Prem Machine
    <b>On-Prem Machine</b>
    Run
    Helix Scripts
    Run <br><b>Helix Scripts</b>
    Add Artifacts
    Add <b>Artifacts</b>
    Create Image
    Create <b>Image</b>
    Create Scaleset
    Create <b>Scaleset</b>
    Test VM
    Test VM
    Test Helix Scripts
    Test Helix Scripts
    Test upgrade on On-Prem
    Test upgrade on On-Prem
    Test Artifacts
    Test Artifacts
    Test Image
    Test Image
    Test Scaleset
    Test Scaleset
    Run Bootstrapper
    Run <b>Bootstrapper</b>
    Test Bootstrapper
    Test Bootstrapper
    \ No newline at end of file Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/Images/scenariotestprojects.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/Images/scenariotestprojects.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/Images/Stages.PNG and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/Images/Stages.PNG differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/Images/testproject.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/Images/testproject.png differ diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/Images/ValidationDeploymentWorkflow.svg dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/Images/ValidationDeploymentWorkflow.svg --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/Images/ValidationDeploymentWorkflow.svg 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/Images/ValidationDeploymentWorkflow.svg 1970-01-01 00:00:00.000000000 +0000 @@ -1,429 +0,0 @@ - - - - - - - - - - -
    -
    Developer
    -
    -
    - Developer -
    -
    - - - - - -
    -
    Service
    -
    -
    - Service -
    -
    - - - - -
    -
    Service Unit Tests
    -
    -
    - Service Unit Tests -
    -
    - - - - -
    -
    Service Scenario Tests
    -
    -
    - Service Scenario Tests -
    -
    - - - - - - - - - -
    -
    PR Pipeline
    -
    -
    - PR Pipeline -
    -
    - - - - -
    -
    Merge to Master branch
    -
    -
    - Merge to Master branch -
    -
    - - - - -
    -
    - Pre-deployment -
    - checks and scenario tests for staging -
    -
    -
    - [Not supported by viewer] -
    -
    - - - - -
    -
    Deploy to Staging Pipeline
    -
    -
    - Deploy to Staging Pipeline -
    -
    - - - - -
    -
    Post-deployment checks and scenario tests for staging
    -
    -
    - [Not supported by viewer] -
    -
    - - - - -
    -
    Merge to Production branch
    -
    -
    - Merge to Production branch -
    -
    - - - - -
    -
    - Pre-deployment -
    - checks and scenario tests for -
    - production -
    -
    -
    - [Not supported by viewer] -
    -
    - - - - -
    -
    Deploy to Production Pipeline
    -
    -
    - Deploy to Production Pipeline -
    -
    - - - - -
    -
    Post-deployment checks and scenario tests for production
    -
    -
    - [Not supported by viewer] -
    -
    - - - - -
    -
    Code live on Production
    -
    -
    - Code live on Production -
    -
    - - - - -
    -
    Nightly job with scenario tests
    -
    -
    - Nightly job with scenario tests -
    -
    - - - - -
    -
    Test Results reported on Azure DevOps dashboard
    -
    -
    - Test Results reported on Azure DevOps dashboard -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    Need to investigate issues found with staging environment
    -
    -
    - [Not supported by viewer] -
    -
    - - - - - - - - -
    -
    Need to investigate issues found with production environment
    -
    -
    - [Not supported by viewer] -
    -
    - - - - - - - - -
    -
    Rollback to previous deployment
    -
    -
    - Rollback to previous deployment -
    -
    - - - -
    -
    This is the code that the developer is responsible for writing for their feature
    -
    -
    - [Not supported by viewer] -
    -
    - - - - - -
    -
    Pass
    -
    -
    - Pass -
    -
    - - - -
    -
    Pass
    -
    -
    - Pass -
    -
    - - - -
    -
    Pass
    -
    -
    - Pass -
    -
    - - - -
    -
    Pass
    -
    -
    - Pass -
    -
    - - - - - -
    -
    Fail
    -
    -
    - Fail -
    -
    - - - -
    -
    Fail
    -
    -
    - Fail -
    -
    - - - -
    -
    Fail
    -
    -
    - Fail -
    -
    - - - -
    -
    Fail
    -
    -
    - Fail -
    -
    - - - -
    -
    Fail
    -
    -
    - Fail -
    -
    - - - -
    -
    Fail
    -
    -
    - Fail -
    -
    - - - -
    -
    Test Results
    -
    -
    - Test Results -
    -
    - - - -
    -
    Test Results
    -
    -
    - Test Results -
    -
    - - - -
    -
    Test Results
    -
    -
    - Test Results -
    -
    - - - -
    -
    Test Results
    -
    -
    - Test Results -
    -
    -
    -
    \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/OSOBValidation.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/OSOBValidation.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/OSOBValidation.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/OSOBValidation.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -# Validation for OS OnBoarding (OSOB) - -## Pipeline and Jobs - -**PRs** :
    - [Build -> Build Images -> Approval -> Pre-Deployment -> Deploy Queues & Deploy AutoScale Service -> Validate & Post-Deployment](https://dnceng.visualstudio.com/internal/_build?definitionId=596&_a=summary) -- Runs unit tests in the `Helix.Machines` solution (e.g. testing scripts) -- Collects and reports code coverage -- Artifact validation on selected images in two phases: once just after artifacts has been installed in ImageFactory, once as a part of test job for a test queue -- Validate whether the yamls are properly constructed ie if the artifacts and Images are correct and they exist. -- Create images, deploy images and artifacts (in staging), update test OnPrem machines with Helix Client, and send jobs to selected queues. (This is broken right now - issue tracked [here](https://github.com/dotnet/core-eng/issues/7984)) - - Supported OnPrem Queues: found in file `validation\onprem.pr.queues.txt` - -**CI** :
    - [Build -> Build Images -> Approval -> Pre-Deployment -> Deploy Queues & Deploy AutoScale Service -> Validate & Post-Deployment & Cleanup](https://dnceng.visualstudio.com/internal/_build?definitionId=596&_a=summary) -- Everything that runs in PRs but for all queues -- Create images, deploy images and artifacts, update test OnPrem machines with Helix Client, and send jobs to all the queues. - - Supported OnPrem Queues: found in file `validation\onprem.staging.queues.txt` -- Runs clean up stage to clean up old queues on staging - -## Where do the Tests live? -Per the [Validation Process](https://github.com/dotnet/core-eng/blob/main/Documentation/Validation/ValidationProcess.md#unit-testing) documentation, tests will live within the solution of the project being tested. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CValidation%5COSOBValidation.md)](https://helix.dot.net/f/p/5?p=Documentation%5CValidation%5COSOBValidation.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CValidation%5COSOBValidation.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/Overview.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/Overview.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/Overview.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/Overview.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,76 +0,0 @@ -# Arcade Validation - -We need to make sure changes done in the Arcade SDK as well as in the [core packages](https://github.com/dotnet/arcade/tree/master/Documentation/CorePackages) don't break any of the consuming repos or Arcade itself. - -## Arcade Validation Policy - -- Contributors who are changing existing code should use their best judgement to decide if additional validation against the runtime, aspnetcore and installer repos is necessary. The following is a list of situations in which the contributor may want to run their changes against these repos: - - Changes made to Signing, Publishing, or other stages outside of the build stage that would not show up in a PR build. - - Changes that affect a fundamental piece of Arcade (e.g. build scripts, install scripts) - - Changes that affect many files (e.g. refactoring MSBuild Tasks to use a new abstract class for dependency injection support) - - Changes to packages that are only exercised by a specific set of repos, such as the Shared Framework SDK. -- If there are any known breaking changes or any breaking changes surface during the validation, those changes should be communicated per the [Breaking Change Policy](../Policy/ChangesPolicy.md). -- Official Arcade builds from main will now be promoted automatically to `.NET Eng - Latest` channel once it has passed the official Arcade Validation pipeline. - -## The process - -### Self-build - -1. Build the latest changes locally using the specified settings -2. Using darc, update the dependencies based on the packages built in #1 -3. Execute the "official build" (this will restore the packages built in #1) - -To validate against the Arcade Validation for Promotion pipeline (that includes the ability to build Arcade with the bellwether repos), follow these steps (which are similar to the steps outlined here for [How to Validate a Private Build](https://github.com/dotnet/arcade/blob/master/Documentation/Policy/TestingMSBuildGuidance.md#how-to-validate-a-private-build)): - -1. Run a build of your Arcade branch on the [arcade-official-ci](https://dnceng.visualstudio.com/internal/_build?definitionId=6) Azure DevOps Pipeline -2. [Promote your build](../Darc.md#add-build-to-channel) to the "General Testing" Maestro channel. -3. Create a branch of [arcade-validation](https://github.com/dotnet/arcade-validation) -4. Using darc, run `update-dependencies` ([update-dependencies documentation](../Darc.md#updating-dependencies-in-your-local-repository)) on your Arcade Validation branch to use the build of Arcade you just created in the previous steps. -5. Push your branch up to Azure DevOps Arcade Validation repository and run a build of your branch on the [dotnet-arcade-validation-for-promotion](https://dev.azure.com/dnceng/internal/_build?definitionId=838&_a=summary) to verify your changes. -6. It's not necessary to merge your Arcade Validation branch into the repo's main branch, so feel free to delete it when you're done validating your changes. - -### '.NET Eng - Validation' channel - -Arcade's official builds go to the ".NET Eng - Validation" channel. - -### [Arcade-Validation Repository](https://github.com/dotnet/arcade-validation) - -This repository contains the scenarios where we validate the last produced version of the SDK as a consumer repository. - -#### Validation Process - -1. On every Arcade build dependencies will be updated and auto-merged when all the checks pass -2. Arcade validation [official build](https://dnceng.visualstudio.com/internal/_build?definitionId=282) -is triggered. This will validate the version which was just “pushed” by Arcade - -#### Validation Scenarios - -The following scenarios are a part of [Arcade Validation](https://github.com/dotnet/arcade-validation)'s build process. - -**Build** - -The code in the repo is built using the pushed in version of Arcade SDK and core packages. During this phase, packaging, signing and publishing are validated automatically. - -**[Signing](https://github.com/dotnet/arcade-validation/tree/master/eng/validation/templates/signing)** - -Test and real signing are validated by attempting to sign files [here](https://github.com/dotnet/arcade-validation/tree/master/src/Validation/Resources). -Since we download all the files in the folder we can easily modify the amount and type of files we sign. - -Details on how the Signing package works [here](https://github.com/dotnet/arcade/blob/master/Documentation/CorePackages/Signing.md). - -**[Testing](https://github.com/dotnet/arcade-validation/tree/master/eng/validation/templates/testing) (Send jobs to Helix)** - -We validate external (anonymous) and internal scenarios of sending jobs to Helix. - -Details on how sending jobs to Helix works [here](https://github.com/dotnet/arcade/blob/master/Documentation/AzureDevOps/SendingJobsToHelix.md). - -**Publishing to BAR** - -When the Build portion of the validation build completes we publish the produced package to BAR. This package won't be consumed by any repo but we want to make sure changes in the SDK did not affect it. - -While this is good enough to validate basic scenarios we still have to make sure we validate changes in tier one repos. This will be done post preview 2 as specified in [this](https://github.com/dotnet/arcade/issues/111) epic. - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CValidation%5COverview.md)](https://helix.dot.net/f/p/5?p=Documentation%5CValidation%5COverview.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CValidation%5COverview.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/README.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/README.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/README.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/README.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,368 +0,0 @@ -# Validation Principles and Policy - -- All code changes (e.g. new features and functionality, refactored code, impact of removed code, et cetera) must be testable in staging. -- All code changes must be described in an issue, including the motivation for the change and links to corresponding pull requests. -- All test results (including, but not limited to, unit, scenario, functional tests) should be viewable from the same location (e.g. all test results are currently being reported to Azure DevOps Test Results). See section ["Consistency in Reporting"](#consistency-in-reporting) for how this is impelmented. -- We should strive for 80% code coverage for our unit tests. This give us some leeway when we are dealing with generated code which we may not be able to unit test completely, or if we have some legacy code that cannot be easily unit tested and will need to be covered by functional or scenario tests instead. The goal is to make us think about all the possible code paths, possible corner/edge cases, and different types of data that can be passed into our methods that we will want to validate the outcome of. The code coverage reports can help us determine which code paths were missed in our unit tests so that we can meet (or exceed) the goal of 80% code coverage. -- Efforts should be made to ["shift left"](https://en.wikipedia.org/wiki/Shift-left_testing) for testing. Therefore: - - Developers should validate their changes prior to creating a pull request. This may require testing locally on their dev machine or creating a virtual machine in Azure to test an artifact, et cetera. - - Where possible, unit testing should be used. See the [Validation Process](#unit-testing) for guidance for adding unit tests and creating new unit test projects that will hook into pull request and continuous integration job runs. - - Scenario tests should be written to support testing customer use cases. These can be written as a part of the [post-deployment scenario tests](#post-deployment). - - If there are known resources that are required for a deployment to be successful, [pre-deployment checks](#pre-deployment) should be written. - - Functionality that uses dependency injection (DI) requires tests to validate that the dependency injection works. - -# Validation Process Overview - -Business Requirement: The process for running tests and reporting results uses a standard model across all services - -## Validation and Deployment Workflow - -Ideal developer workflow from dev environment to production: -1. Developer is responsible for writing code for feature, unit/functional tests to cover code written for feature, and expanding on any scenario tests that changes or enhances the way the service works. -2. When the feature is completed, the developer should open a pull request that will validate their code. Upon passing validation (and gaining approval on their PR from their peers), the developer should merge into the main branch. -3. [Pre-deployment](#pre-deployment) checks on staging should occur to see if the services in staging are healthy for us to deploy to. If not, we should investigate and resolve the problems before we can deploy. -4. Code is built from main and deployed to staging. -5. [Post-deployment](#post-deployment) checks on staging should occur to ensure that the code we deployed is working as intended. -6. Similarly, we'll have pre-deployment checks on production prior to deploying our code to production. -7. Code is deployed to production. -8. Post-deployment validation: - 1. Similar post-deployment checks on production, if necessary. - 2. A [nightly](#nightly) job, currently configured to run only on the staging environment, handles scenario tests that produce more load on our staging environment than we want to be running during regular business hours when staging is used for regular PR and CI jobs. - -![Validation and Deployment Workflow](Images/ValidationDeploymentWorkflow.svg) - -## Specific Validation for Services Covered ## - -The following links are to documents regarding specifics for validating these services. It includes links to pipelines and jobs, definitions of specific scenarios and things being tested for that service, - -- [Helix Services and API](HelixValidation.md) -- [OS Onboarding](OSOBValidation.md) -- [Arcade](https://github.com/dotnet/arcade/blob/main/Documentation/Validation/Overview.md) -- Arcade Services - -## Consistency in Testing Services - -All the test projects for all services should exist in the same solution as the service being tested. The following documentation is guidance for how to set up unit tests, scenario tests, and how to categorize tests. - -### Test Categorization - -Categorization of our C# tests allow us to streamline the running of tests in our Azure DevOps pipelines. By organizing them this way, we can identify the category of tests that we want to run at a given time. For example, all the tests categorized with `PostDeployment` and `Staging` are configured up to run after a deployment to staging has occurred. - -Adding a category to a test (or a whole test class or test project) in [NUnit](https://github.com/nunit/docs/wiki/Category-Attribute) can be done as follows: - -``` -[Category("PostDeployment", "Staging")] -public void TestName... -``` - -This shows that the test called `TestName` has the categories of `PostDeployment` and `Staging`. If a test run task in Azure DevOps ran either of those categories, it would run that test. If it required both of those categories, it would also run. - -*Note: For [XUnit tests](http://www.brendanconnolly.net/organizing-tests-with-xunit-traits/), you'll need to use the `Trait` attribute instead of `Category`.* - -*Note: We currently are supporting both XUnit and NUnit testing frameworks. However, we prefer to use XUnit where possible. NUnit was introduced only to support parallelization for our functional and scenario tests. If neither one of these testing frameworks works for your testing use case, start a discussion with the team to determine a solution. See [this discussion](https://github.com/dotnet/core-eng/pull/8568#discussion_r377821502) for more information.* - -The following are categories that have been defined so far, but we are not limited to only using these categories. These categories are not mutually exclusive, so if there are tests that should be run in both Production and Staging, they can be categorized as such. - -#### Pre-Deployment -Criteria: -- Does the test validate the state of the environment being deployed to? -- Does the test validate the existence of necessary resources for deployment? - -Examples of pre-deployment scenario tests: -- Can we connect to the Service Fabric cluster that we need to deploy to? -- Do the secrets we use exist in the appropriate Key Vaults? -- Do the Resource Groups required exist in Azure? - -*Category Attribute Value*: `PreDeployment` - -#### Post-Deployment -Criteria: -- Does the test validate the state of the environment after deployment? -- Does the test require other services in Azure (e.g. Service Bus, Scalesets) to run? -- Do we not have the ability to run this test locally on a dev environment? - -Examples of post-deployment scenario tests: -- Testing endpoints in Helix API -- Deployed service health check (e.g. are they running, are they returning expected results, et cetera) -- Was the Helix database schema changed appropriately? - -*Category Attribute Value*: `PostDeployment` - -#### Nightly -Criteria: -- Does the test do things that may disrupt normal work? -- Do we want the test to run on a regular cadence instead of on-demand? - -Examples of nightly scenario tests: -- Service Fabric Chaos test -- Load testing sending jobs to Helix queues - -*Category Attribute Value*: `Nightly` - -#### Staging -Criteria: -- Does the test validate functionality prior to being rolled out to production? -- Does the test validate the state of the staging environment? - -Examples of staging scenario tests: -- Testing jobs sent to Helix to validate deployments to staging. We do this on staging only because if we were to run them on Production they would end up behind customer jobs, and customers would catch the bugs before our tests would. - -*Category Attribute Value*: `Staging` - -#### Production -Criteria: -- Does the test monitor or test functionality after it has been deployed to production? -- Does the test validate the state of the production environment? - -Examples of production scenario tests: -- Verifying the existence of required resource groups on the Production environment. -- Tests that would monitor the health of our services and queues (not necessarily tied to a post-deployment scenario). - -*Category Attribute Value*: `Production` - -### Scenario Tests - -Scenario tests are defined as a test that encompasses a full functionality path (which is the scenario). There is an expected outcome for these tests based on the input provided. Usually, these tests will span multiple methods or even services. For example, we can create a test that creates a job, sends the work item to a specific queue, and verifies the job entry in our SQL database. This scenario would test multiple services and functionality in our ecosystem, such as the Helix API, Helix Controller, OS Onboarding, et cetera. - -Scenario tests should encompass things like common functionality used by our customers, functionality that was able to reproduce a problem in our service (e.g. load test that was able to reproduce [the issue](https://github.com/dotnet/core-eng/issues/7548) in AppInsights), et cetera. The tests should be categorized so that they can be run during specific pipelines. These tests are also useful for reproducing issues found by customers and building up a test suite to prevent future regressions, or ensuring that any refactoring that takes place will still provide the same expected result. - -Scenario Test Criteria: -- The test will cover all the functionality hit during a specific customer scenario. (e.g. Customer sends a job to a specific Helix queue that does some specific work) -- There is an expected result for the test. -- The test can be repeated with the same expected outcome. -- Other expected results can be validated (e.g. data sent to SQL or Kusto, state changes for other resources) along the way. - -Scenario test projects should be configured as follows: - -1. Create a folder called `Validation` within the solution for the service. - -2. Create a C# test project for the type of the tests you want to add. Example: - -![scenariotestprojects](Images/scenariotestprojects.png) - -The project name should include the service being tested, that it is a test project, the environment that will be targeted, and when the tests will run. In the above screenshot, we have `Helix.Test.Staging.Nightly` which denotes that this test project will run nightly on staging for Helix tests. The `Helix.Test.Utilities` project is a helper project that does not contain tests. - -3. Each test project should contain a `AssemblyInfo.cs` file that should contain the Category Attributes for the target environment and when the tests should run. For example, in NUnit: - -``` -[assembly: Category("Nightly")] -[assembly: Category("Staging")] -``` - -### Unit Testing - -Unlike scenario tests, [unit tests](https://docs.microsoft.com/en-us/dotnet/core/testing/#what-are-unit-tests) should be targeted to a specific piece of functionality, preferably within a single method. An example of a unit test would be validating that a method that calculates a value does it correctly or that it handles bad input gracefully (or returns some kind of error or other expected response). Unit tests should not require additional services in order to verify the test. Services/resources/classes/modules/et cetera outside the scope of the test should be mocked out. These tests should also be able to be ran locally on any developer's environment. Being able to run the tests locally allows developers to validate that code before committing it to the repository, or have to rely on a pull request to validate their changes. - -When developing unit tests, you may find it appropriate to mock out dependencies to support the test. Our preferred mocking framework is [Moq](https://github.com/Moq/moq4/wiki/Quickstart) for our C# code. You might also find it necessary to use adapter/wrapper patterns to support mocking out dependencies (e.g. see [TelemetryClientProxy.cs](https://dnceng.visualstudio.com/internal/_git/dotnet-helix-service?path=%2Fsrc%2FUtility%2FHelix.Utility.Logging%2FTelemetryClientProxy.cs) class created to support mocking the `TelemetryClient` class from `Microsoft.ApplicationInsights` library.) - -(Dec 12, 2019) Note: These steps have been validated to work in the Helix Services and Arcade Services, and still need to be validated to work with OSOB. - -To add a unit test project, and ensure that it is able to automatically run in the pipelines and have it's test results and code coverage collected, do the following steps: - -1. Create the new test project (using xUnit): Right click on the solution (or folder where you want to add the test project) -> Add -> New Project... -> select xUnit Test Project (.NET Core) - -![newxunitproject](Images/newxunitproject.png) - -Alternatively, you can create a new project via the dotnet CLI with the command: `dotnet new xunit` - -Use the same name as the source project the tests will be for, but with ".Tests" at the end. Based on the Validation process diagram, this project should existing beside the project that it will be testing. This allows for better visibility of the test project in relation to the code it will be testing. - -![testproject](Images/testproject.png) - -2. The following package references should be added to the .csproj: -``` - - - - -``` -3. Rename the namespace of the new test class with the rest of the tests in the solution. Example: -`Microsoft.Internal.Helix.Utility.Tests` -4. Include the source project to be tested as a project reference: Right click on the test project -> Add -> Reference... -> Select the checkbox for the source project. This is what it would look like in the .csproj file: -``` - - - -``` -5. Ensure that the test project is targeting the correct Target Framework Moniker (TFM). This should be the consistent with the source project's TFM. Example: -``` - - $(NetFrameworkFramework) - -``` -6. Projects that use the .NET Core framework [require an additional property](https://github.com/Microsoft/vstest/issues/800) to ensure the symbols required for code coverage are published. In each source project being tested (not the test project), add the following: -``` - - Full - -``` -7. Excluding third party libraries from code coverage can be accomplished through [adding a .runsettings file](https://docs.microsoft.com/en-us/visualstudio/test/customizing-code-coverage-analysis?view=vs-2019). This file will need to be referenced in the stage that collects code coverage. The contents of the .runsettings file should look like this: -``` - - - - - - - - - .*Moq.dll - .*fluentassertions.dll - .*microsoft.extensions.logging.applicationinsights.dll - .*libgit2sharp.dll - .*quartz.dll - - - - - - - - -``` - -#### Testing BARViz - -There is [documentation in Arcade Services](https://github.com/dotnet/arcade-services/tree/master/src/Maestro/maestro-angular) regarding steps to test locally. Also, there is a task in Arcade Services pipeline that will run these tests via `npm test` during PRs/CI. - -Notes: -- Currently, this process has only been verified to work with xUnit tests. Although we do have nUnit in our codebase, it was introduced to help with parallelization of our functional and scenario tests. -- Why are the package references in step 2 needed? - - **xunit**: used for the xUnit test framework. - - **xunit.runner.visualstudio**: required for Visual Studio to run the xunit tests. - - **Microsoft.NET.Test.Sdk**: this package (specifically version 15.8.0+) is required so that the `dotnet test` command can collect code coverage during the PR and CI-merge-to-master pipeline runs. - - **Microsoft.CodeCoverage**: this package ensures that the CodeCoverage.exe tool will always be available in our builds, especially to help us reduce our dependency on Visual Studio Enterprise. -- If the repository is using [Arcade](https://github.com/dotnet/arcade), the unit test results will automatically be collected. If it's not, then the build pipeline will require a step to upload the test results. - -## Consistency in Reporting - -When our tests fail, we want to be able to see what failure(s) occurred so that we can remediate the issue(s). We want to have a standard way for the tests to report on the test results, and allow us to see the health of our builds and deployments based on the tests that have ran. - -All results from all types of tests should be uploaded to Azure DevOps test results to have a single location to view our test results. Depending on the Azure DevOps task used, the test results may be uploaded to the Test Results automatically. Although it would be ideal for all our services to run tests the same way, it would be unreasonable to expect that because not all our services are engineered the same way (e.g. Arcade-Services uses Arcade's process to build and upload test results, versus Helix Services that makes use of the VSTest Azure DevOps task to run and upload test results). The expectation is that the test results for any test (whether it's unit or scenario) should show up in Azure DevOps Test Results. - -If a project is not set up to run tests, here are some snippets to help incorporate that functionality into the project's Azure DevOps build/test/deploy pipelines: - -### Using VSTest Task -This is a snippet from the Helix Services `deploy-staging.yaml` for running the post-deployment scenario tests. The VSTest task will automatically upload the results from this task to Azure DevOps Test Results. - -This task will run the tests in `Helix.Test.Staging.PostDeployment.dll` with the test categories of `Staging` and `PostDeployment` -``` -- task: VsTest@2 - displayName: Post Deployment Scenario Tests - ... - inputs: - testSelector: testAssemblies - testAssemblyVer2: | - **\Helix.Test.Staging.PostDeployment.dll - searchFolder: '$(Pipeline.Workspace)/Deploy_Staging' - testFiltercriteria: > - TestCategory=Staging& - TestCategory=PostDeployment - ... - testRunTitle: Post Deployment Scenario Tests -``` - -### Using DotNetCoreCLI Task -This is a snippet from the Helix Services `test.yaml` for running the unit tests, and collecting code coverage reports. Tests results from here are not automatically uploaded, so it will require calling the `PublishTestResults` task as well. - -This task will run any test that fits the pattern of `src/**/*.Tests.csproj`, and omits any test that is categorized for `PostDeployment`, `Nightly`, or `PreDeployment`, since we only want unit tests to run in this task. - -``` -- task: DotNetCoreCLI@2 - displayName: Test C# (dotnet test) - inputs: - command: 'custom' - projects: 'src/**/*.Tests.csproj' - custom: 'test' - arguments: > - --configuration $(BuildConfiguration) - --collect:"Code Coverage" - --settings:$(Build.SourcesDirectory)/src/CodeCoverage.runsettings - --filter "TestCategory!=PostDeployment&TestCategory!=Nightly&TestCategory!=PreDeployment" - --logger trx - --no-build - condition: succeededOrFailed() - -- task: PublishTestResults@2 - displayName: 'Publish Unit Test Results' - inputs: - testResultsFormat: VSTest - testResultsFiles: '**/*.trx' - mergeTestResults: true - searchFolder: $(system.defaultworkingdirectory) - testRunTitle: Helix Unit Tests -``` - -### Code Coverage Reports - -Code coverage reports should also be available for PR/CI pipelines and will report on how much code is covered via unit tests (if configured correctly per the guidance above). At the moment, the reports are generated for only C# unit tests, and uses a series of Azure DevOps tasks to complete. The following code snippets are how code coverage is currently set up for our services that employ it's use (as of Jan 10, 2020, Arcade Services and Helix Services). - -*Note: A fair amount of the following was created leveraging information from [this article](https://medium.com/swlh/generating-code-coverage-reports-in-dotnet-core-bb0c8cca66).* - -Code coverage files are collected using the DotNetCoreCLI task. There were some challenges with doing this, namely that we have to use the `custom` command versus `test` as the `test` command does not support code coverage collection. This task will also run and collect unit test results. -``` -- task: DotNetCoreCLI@2 - displayName: Test C# (dotnet test) - inputs: - command: 'custom' - projects: 'src/**/*.Tests.csproj' - custom: 'test' - arguments: > - --configuration $(BuildConfiguration) - --collect:"Code Coverage" - --settings:$(Build.SourcesDirectory)/src/CodeCoverage.runsettings - --filter "TestCategory!=PostDeployment&TestCategory!=Nightly&TestCategory!=PreDeployment" - --logger trx - --no-build - condition: succeededOrFailed() -``` - -Next, we need to convert the `.coverage` file that was generated in the previous step to XML. The `convert-codecoveragetoxml.ps1` Powershell script finds the `.coverage` file, and uses `CodeCoverage.exe` to convert to XML. (*Note: This Powershell script file is a custom script that is added to each /eng/ folder in repositories that do this work.*) -``` -- task: Powershell@2 - inputs: - targetType: 'filePath' - filePath: eng\convert-codecoveragetoxml.ps1 - arguments: -Path "$(system.defaultworkingdirectory)" -NugetPackagesPath "$(Build.SourcesDirectory)\packages" - displayName: Convert Code Coverage to XML (powershell) -``` - -To generate the fancy report, we use the ReportGenerator Azure DevOps plug-in. -``` -- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4 - displayName: ReportGenerator - inputs: - reports: '$(system.defaultworkingdirectory)\codecoverage.coveragexml' - targetdir: '$(Build.SourcesDirectory)\CodeCoverage' - reporttypes: 'HtmlInline_AzurePipelines;Cobertura' - sourcedirs: '$(Build.SourcesDirectory)' -``` - -Lastly, we need to upload both the code coverage report that was generated from the previous step and the unit test results from the above test run. -``` -- task: PublishCodeCoverageResults@1 - displayName: 'Publish Code Coverage' - inputs: - codeCoverageTool: Cobertura - summaryFileLocation: '$(Build.SourcesDirectory)\CodeCoverage\Cobertura.xml' - reportDirectory: '$(Build.SourcesDirectory)\CodeCoverage' - pathToSources: '$(Build.SourcesDirectory)' - publishRunAttachments: true - -- task: PublishTestResults@2 - displayName: 'Publish Unit Test Results' - inputs: - testResultsFormat: VSTest - testResultsFiles: '**/*.trx' - mergeTestResults: true - searchFolder: $(system.defaultworkingdirectory) - testRunTitle: Helix Unit Tests -``` - -#### Code Coverage Reports Locally - -To get a report of code coverage locally, you can run the Code Coverage Analyzer from Visual Studio Enterprise. Follow the instructions [here](https://docs.microsoft.com/en-us/visualstudio/test/using-code-coverage-to-determine-how-much-code-is-being-tested?view=vs-2019) for how to run and view code coverage results. - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CValidation%5CREADME.md)](https://helix.dot.net/f/p/5?p=Documentation%5CValidation%5CREADME.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CValidation%5CREADME.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/TestingGuidance.md dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/TestingGuidance.md --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/Documentation/Validation/TestingGuidance.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/Documentation/Validation/TestingGuidance.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -# Testing Guidance - -## Requirements - -### [FluentAssertions](https://fluentassertions.com/introduction) -We are using FluentAssertions for all testing assertions. -The main benefit specific to this repository is that this allows us to use the same assertion library regardless of execution frameworks (XUnit or NUnit), -so that a developer only needs to learn how to write and read a single assertion library to be productive. -Other benefits are outlined well in the documentation on the [FluentAssertions site](https://fluentassertions.com/introduction). - -### [NUnit](https://docs.nunit.org/articles/nunit/intro.html) -We are going to use NUnit as the testing framework, but _not_ the assertion library. -This means using the attributes, like `[Test]`, but not the `Assert` class. -This is because we have a lot of integration testing, where each test can take a long time, -and having fine grained control of the parallelism of execution is critical so that these tests can finish in a reasonable amount of time. -With XUnit, this requires putting every test in a separate class or "Test Collection", -which impacts maintainability more than using NUnit. - -## Tips - -There are a lot of good guides on the internet about how to write good tests. - -I highly recommend watching this training day video as well -[Unit Tests that Don't Suck - by Graham Wheele](https://msit.microsoftstream.com/video/93e9a3ff-0400-a936-f58b-f1eaa13859f9) - -Here are some good articles I read on the subject that align with our testing usage. -* [Unit Tests, How to Write Testable Code and Why it Matters - Sergey Kolodiy](https://www.toptal.com/qa/how-to-write-testable-code-and-why-it-matters) -* [13 Tips for Writing Useful Unit Tests - Nick Hodges](https://medium.com/better-programming/13-tips-for-writing-useful-unit-tests-ca20706b5368) -* [You Still Don’t Know How to Do Unit Testing (and Your Secret is Safe with Me) - Erik Dietrich](https://stackify.com/unit-testing-basics-best-practices/) - -### Takeaways - -#### Avoid stateful classes -In general, `[SetUp]` and `[TearDown]` (or constructor/Dipose) methods should be avoided. -And `[SetUp]`/`[TearDown]` in base classes is especially bad. -Not only does this mean that to understand a test you need to go spelunking in multiple files to attempt to track down what's happening, -it also means that the behavior of your test is tightly coupled to the test infrastructure. - -In general you get a lot more control by having a class that encapsulates the data your tests need. -In various reading, this is often called an "Object Mother" (example here https://reflectoring.io/objectmother-fluent-builder/). -A test method calls the required methods in the beginning of the test method, -and, if necessary, can "using" any of the returned objects that need to be disposed. -The benefits of this are that everything the test does is done in the test itself -(or methods called directly by the tests, so you can F12 your way into them if necessary). - -#### Be verbose -In tests, it is especially helpful for things to be named verbosely. -Test methods, variable names, any test factory methods that are called, -adding string to assertions that might otherwise be unclear -(if you are asserting multiple properties in a method, it is helpful to say which property each assert is for, -so, it is not "expected: 2, actual: 4" but "property exampleProperty.Length, expected: 2, actual: 4".) - -Often developers will have only the name of the method and the assert message to go on, -and then might be unfamiliar with the code that is failing (if they were, they likely wouldn't have broken it, after all). -The more information that you can put as close to the failure as possible, the more likely the problem can be addressed quickly and correctly). - -#### Use [Dependency Injection](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1), even for small things, like "Now" -The dependency injection we use for all our code is `Microsoft.Extensions.DependencyInjection`. -It's relatively lightweight, built in to ASP.NET Core, and can be used with little effort to solve a large host of problems. -And it can greatly simplify testing and sharing code. - -If you find yourself calling some static, stateful method if instead an injected Singleton class/interface might improve the ability to test. -Even innocuous things, like `DateTime.UtcNow`, can almost singlehandedly make testing a function impossible. -Inject an `ISystemClock` instead. - -If you find that your class is injecting a large number of dependencies, it's likely a sign the class is trying to do too much -(and at the very least, will be difficult to test since every dependency needs to be accounted for in a test). - - - -Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CValidation%5CTestingGuidance.md)](https://helix.dot.net/f/p/5?p=Documentation%5CValidation%5CTestingGuidance.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CValidation%5CTestingGuidance.md) - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/eng/common/sdk-task.ps1 dotnet8-8.0.100-8.0.0/src/arcade/eng/common/sdk-task.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/eng/common/sdk-task.ps1 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/eng/common/sdk-task.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -64,7 +64,7 @@ $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty } if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { - $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.6.0-2" -MemberType NoteProperty + $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.8.1-2" -MemberType NoteProperty } if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") { $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/eng/common/tools.ps1 dotnet8-8.0.100-8.0.0/src/arcade/eng/common/tools.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/eng/common/tools.ps1 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/eng/common/tools.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -379,13 +379,13 @@ } # Minimum VS version to require. - $vsMinVersionReqdStr = '17.6' + $vsMinVersionReqdStr = '17.7' $vsMinVersionReqd = [Version]::new($vsMinVersionReqdStr) # If the version of msbuild is going to be xcopied, # use this version. Version matches a package here: - # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/RoslynTools.MSBuild/versions/17.6.0-2 - $defaultXCopyMSBuildVersion = '17.6.0-2' + # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/RoslynTools.MSBuild/versions/17.8.1-2 + $defaultXCopyMSBuildVersion = '17.8.1-2' if (!$vsRequirements) { if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/eng/Version.Details.xml dotnet8-8.0.100-8.0.0/src/arcade/eng/Version.Details.xml --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/eng/Version.Details.xml 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/eng/Version.Details.xml 2023-11-13 13:20:34.000000000 +0000 @@ -7,50 +7,50 @@ https://github.com/dotnet/roslyn c3cc1d0ceeab1a65da0217e403851a1e8a30086a - + https://dev.azure.com/dnceng/internal/_git/dotnet-symuploader - 62ceb439e80bf0814d0ffa17f022d4624ea4aa6c + d617bc8ed2787c235a57cf0dcdfd087b86ff9521 - + https://dev.azure.com/dnceng/internal/_git/dotnet-symuploader - 62ceb439e80bf0814d0ffa17f022d4624ea4aa6c + d617bc8ed2787c235a57cf0dcdfd087b86ff9521 - + https://github.com/dotnet/templating - e4dd6740974287705a91918d808333e220e717d8 + 4a9eb30212b51dc53c2f647843db6aad6c5426a8 - + https://github.com/dotnet/arcade - 90c167d5c57de4a8bced566379dbd893556c94e8 + 1d451c32dda2314c721adbf8829e1c0cd4e681ff - + https://github.com/dotnet/arcade - 90c167d5c57de4a8bced566379dbd893556c94e8 + 1d451c32dda2314c721adbf8829e1c0cd4e681ff - + https://github.com/dotnet/arcade - 90c167d5c57de4a8bced566379dbd893556c94e8 + 1d451c32dda2314c721adbf8829e1c0cd4e681ff - + https://github.com/dotnet/arcade - 90c167d5c57de4a8bced566379dbd893556c94e8 + 1d451c32dda2314c721adbf8829e1c0cd4e681ff - + https://github.com/dotnet/arcade - 90c167d5c57de4a8bced566379dbd893556c94e8 + 1d451c32dda2314c721adbf8829e1c0cd4e681ff - + https://github.com/dotnet/arcade-services - 66dbe283fba4de8610ddac54b57246ed42403d43 + 35362ab2e8b702ebae7b31c0d9cd087b987e118a - + https://github.com/dotnet/arcade-services - 66dbe283fba4de8610ddac54b57246ed42403d43 + 35362ab2e8b702ebae7b31c0d9cd087b987e118a - + https://github.com/dotnet/xharness - 1c09ef5b669c11e1aeca92089d0c1e4408169582 + 2b1d423ce08e1ed78c0a821d0850e0f5ab3b193a https://github.com/dotnet/roslyn @@ -68,9 +68,9 @@ https://github.com/dotnet/symreader-converter c5ba7c88f92e2dde156c324a8c8edc04d9fa4fe0 - + https://github.com/dotnet/xliff-tasks - 194f32828726c3f1f63f79f3dc09b9e99c157b11 + 73f0850939d96131c28cf6ea6ee5aacb4da0083a @@ -85,14 +85,14 @@ 547c506abe05e510bd43330fc8f6d4c5961e9223 - + https://github.com/dotnet/source-build-reference-packages - 93c23409e630c4f267234540b0e3557b76a53ef4 + 0650b50b2a5263c735d12b5c36c5deb34e7e6b60 - + https://github.com/dotnet/source-build-externals - e3cc6c792114ebdfe6627742d2820dbe1ae5bc47 + e04156dbe14f882a80d4499dbebd45ab156b6c3c diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/eng/Versions.props dotnet8-8.0.100-8.0.0/src/arcade/eng/Versions.props --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/eng/Versions.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/eng/Versions.props 2023-11-13 13:20:34.000000000 +0000 @@ -8,12 +8,12 @@ - 8.0.0-beta.23425.2 - 8.0.0-beta.23425.2 - 8.0.0-beta.23425.2 + 8.0.0-beta.23463.1 + 8.0.0-beta.23463.1 + 8.0.0-beta.23463.1 - 1.1.0-beta.23428.1 - 1.1.0-beta.23428.1 + 1.1.0-beta.23477.2 + 1.1.0-beta.23477.2 1.1.1 4.5.5 @@ -21,8 +21,8 @@ 1.0.0-preview6.1.23159.4 - 2.0.0-preview.1.21526.15 - 2.0.0-preview.1.21526.15 + 2.0.0-preview.1.23470.14 + 2.0.0-preview.1.23470.14 6.0.100-1.22103.2 @@ -67,15 +67,15 @@ 1.1.0-beta2-19575-01 - 8.0.100-rc.2.23424.1 + 8.0.100-rtm.23479.1 17.5.0 - 8.0.0-prerelease.23421.1 + 8.0.0-prerelease.23477.1 - 1.0.0-beta.23426.1 + 1.0.0-beta.23475.1 - 1.31.0 + 1.34.0 12.16.0 2.5.0 1.0.1 @@ -98,7 +98,7 @@ 7.10.6071 5.3.0.1 4.18.4 - 13.0.1 + 13.0.3 0.41.0 2.0.3 2.4.2 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/global.json dotnet8-8.0.100-8.0.0/src/arcade/global.json --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/global.json 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/global.json 2023-11-13 13:20:34.000000000 +0000 @@ -1,10 +1,10 @@ { "tools": { - "dotnet": "8.0.100-preview.7.23376.3" + "dotnet": "8.0.100-rtm.23506.1" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.23425.2", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.23425.2", + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.23463.1", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.23463.1", "Microsoft.Build.NoTargets": "3.7.0" } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/scripts/create-preview-flow.ps1 dotnet8-8.0.100-8.0.0/src/arcade/scripts/create-preview-flow.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/scripts/create-preview-flow.ps1 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/scripts/create-preview-flow.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -88,11 +88,10 @@ MakeDefaultChannel https://github.com/dotnet/windowsdesktop $RuntimeBranch $RuntimeChannel MakeDefaultChannel https://github.com/dotnet/wpf $RuntimeBranch $RuntimeChannel MakeDefaultChannel https://github.com/dotnet/winforms $RuntimeBranch $RuntimeChannel -MakeDefaultChannel https://github.com/dotnet/linker $RuntimeBranch $RuntimeChannel if ($AddInternalFlow) { # Because of where internal fixes tend to be, we eliminate some leaves in the graph - # and flow them through the normal public channels: emsdk, icu, linker. + # and flow them through the normal public channels: emsdk, icu. # wpf-int is flowed from the public channel (there is no internal branch or merge) due to # an issue in the staging pipeline where it will give the test feed for the public preview build # internal permissions in some cases because it sees the internal repo publishing locations before @@ -139,7 +138,6 @@ AddArcadeFlow https://github.com/dotnet/windowsdesktop $RuntimeBranch AddArcadeFlow https://github.com/dotnet/wpf $RuntimeBranch AddArcadeFlow https://github.com/dotnet/winforms $RuntimeBranch -AddArcadeFlow https://github.com/dotnet/linker $RuntimeBranch AddArcadeFlow https://github.com/dotnet/installer $SdkBranch AddArcadeFlow https://github.com/dotnet/sdk $SdkBranch AddArcadeFlow https://github.com/dotnet/roslyn-analyzers $SdkBranch @@ -148,7 +146,6 @@ Write-Host "Adding runtime -> runtime flow" AddFlow https://dev.azure.com/dnceng/internal/_git/dotnet-wpf-int $RuntimeChannel https://github.com/dotnet/wpf $RuntimeBranch EveryBuild AddBatchedFlow https://github.com/dotnet/efcore $RuntimeChannel https://github.com/dotnet/aspnetcore $RuntimeBranch EveryBuild -AddBatchedFlow https://github.com/dotnet/emsdk $RuntimeChannel https://github.com/dotnet/aspnetcore $RuntimeBranch EveryBuild AddFlow https://github.com/dotnet/emsdk $RuntimeChannel https://github.com/dotnet/runtime $RuntimeBranch EveryBuild AddFlow https://github.com/dotnet/icu $RuntimeChannel https://github.com/dotnet/runtime $RuntimeBranch EveryBuild AddBatchedFlow https://github.com/dotnet/runtime $RuntimeChannel https://github.com/dotnet/aspnetcore $RuntimeBranch EveryBuild @@ -156,7 +153,6 @@ AddFlow https://github.com/dotnet/runtime $RuntimeChannel https://github.com/dotnet/winforms $RuntimeBranch EveryBuild AddFlow https://github.com/dotnet/winforms $RuntimeChannel https://github.com/dotnet/wpf $RuntimeBranch EveryBuild AddFlow https://github.com/dotnet/wpf $RuntimeChannel https://github.com/dotnet/windowsdesktop $RuntimeBranch EveryBuild -AddFlow https://github.com/dotnet/linker $RuntimeChannel https://github.com/dotnet/runtime $RuntimeBranch EveryBuild if ($AddInternalFlow) { Write-Host "Adding internal runtime -> internal runtime flow" @@ -176,7 +172,6 @@ AddFlow https://github.com/dotnet/windowsdesktop $RuntimeChannel https://github.com/dotnet/sdk $SdkBranch EveryBuild AddFlow https://github.com/dotnet/runtime $RuntimeChannel https://github.com/dotnet/sdk $SdkBranch EveryBuild AddFlow https://github.com/dotnet/runtime $RuntimeChannel https://github.com/dotnet/templating $SdkBranch EveryBuild -AddFlow https://github.com/dotnet/linker $RuntimeChannel https://github.com/dotnet/sdk $SdkBranch EveryBuild if ($AddInternalFlow) { Write-Host "Adding internal runtime->internal sdk flow" diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.Arcade.Sdk/tools/Build.proj dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.Arcade.Sdk/tools/Build.proj --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.Arcade.Sdk/tools/Build.proj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.Arcade.Sdk/tools/Build.proj 2023-11-13 13:20:34.000000000 +0000 @@ -175,6 +175,7 @@ <_SolutionBuildProps Include="@(_CommonProps)"/> <_SolutionBuildProps Include="__DeployProjectOutput=$(Deploy)" Condition="'$(Deploy)' != ''"/> + <_SolutionBuildProps Include="__ImportPackTargets=true" Condition="'$(Pack)' == 'true'" /> + + + + $(GenerateNuspecDependsOn); _ValidationSymbolPackageFormat + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.Arcade.Sdk/tools/SourceBuild/SourceBuildIntermediate.proj dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.Arcade.Sdk/tools/SourceBuild/SourceBuildIntermediate.proj --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.Arcade.Sdk/tools/SourceBuild/SourceBuildIntermediate.proj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.Arcade.Sdk/tools/SourceBuild/SourceBuildIntermediate.proj 2023-11-13 13:20:34.000000000 +0000 @@ -80,8 +80,14 @@ - - + + $(CurrentRepoSourceBuildArtifactsDir) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/PublishingConstants.cs dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/PublishingConstants.cs --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/PublishingConstants.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/PublishingConstants.cs 2023-11-13 13:20:34.000000000 +0000 @@ -967,6 +967,16 @@ symbolTargetType: PublicAndInternalSymbolTargets, filenamesToExclude: FilenamesToExclude, flatten: false), + // VS 17.9, + new TargetChannelConfig( + id: 4015, + isInternal: false, + publishingInfraVersion: PublishingInfraVersion.Latest, + akaMSChannelNames: new List(), + targetFeeds: DotNetToolsFeeds, + symbolTargetType: PublicAndInternalSymbolTargets, + filenamesToExclude: FilenamesToExclude, + flatten: false), }; #endregion } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/Microsoft.DotNet.Build.Tasks.TargetFramework.csproj dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/Microsoft.DotNet.Build.Tasks.TargetFramework.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/Microsoft.DotNet.Build.Tasks.TargetFramework.csproj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/Microsoft.DotNet.Build.Tasks.TargetFramework.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -19,6 +19,8 @@ + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.Build.Tasks.Workloads/src/Microsoft.DotNet.Build.Tasks.Workloads.csproj dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.Build.Tasks.Workloads/src/Microsoft.DotNet.Build.Tasks.Workloads.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.Build.Tasks.Workloads/src/Microsoft.DotNet.Build.Tasks.Workloads.csproj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.Build.Tasks.Workloads/src/Microsoft.DotNet.Build.Tasks.Workloads.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -26,6 +26,8 @@ + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.Helix/Sdk/tools/dotnet-cli/DotNetCli.props dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.Helix/Sdk/tools/dotnet-cli/DotNetCli.props --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.Helix/Sdk/tools/dotnet-cli/DotNetCli.props 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.Helix/Sdk/tools/dotnet-cli/DotNetCli.props 2023-11-13 13:20:34.000000000 +0000 @@ -2,7 +2,7 @@ false - 8.0.0-preview.7.23375.9 + 8.0.0-rtm.23502.22 runtime $(BundledNETCoreAppPackageVersion) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.NuGetRepack/tasks/Microsoft.DotNet.NuGetRepack.Tasks.csproj dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.NuGetRepack/tasks/Microsoft.DotNet.NuGetRepack.Tasks.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.NuGetRepack/tasks/Microsoft.DotNet.NuGetRepack.Tasks.csproj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.NuGetRepack/tasks/Microsoft.DotNet.NuGetRepack.Tasks.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -11,6 +11,8 @@ + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.PackageTesting/Microsoft.DotNet.PackageTesting.csproj dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.PackageTesting/Microsoft.DotNet.PackageTesting.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.PackageTesting/Microsoft.DotNet.PackageTesting.csproj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.PackageTesting/Microsoft.DotNet.PackageTesting.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -8,6 +8,8 @@ + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.SharedFramework.Sdk/Microsoft.DotNet.SharedFramework.Sdk.csproj dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.SharedFramework.Sdk/Microsoft.DotNet.SharedFramework.Sdk.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.SharedFramework.Sdk/Microsoft.DotNet.SharedFramework.Sdk.csproj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.SharedFramework.Sdk/Microsoft.DotNet.SharedFramework.Sdk.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -12,6 +12,8 @@ + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.SignTool/Microsoft.DotNet.SignTool.csproj dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.SignTool/Microsoft.DotNet.SignTool.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.SignTool/Microsoft.DotNet.SignTool.csproj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.SignTool/Microsoft.DotNet.SignTool.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -24,6 +24,8 @@ + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.SourceBuild/tasks/Microsoft.DotNet.SourceBuild.Tasks.csproj dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.SourceBuild/tasks/Microsoft.DotNet.SourceBuild.Tasks.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/src/Microsoft.DotNet.SourceBuild/tasks/Microsoft.DotNet.SourceBuild.Tasks.csproj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/src/Microsoft.DotNet.SourceBuild/tasks/Microsoft.DotNet.SourceBuild.Tasks.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -11,6 +11,8 @@ + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/arcade/.vault-config/dnceng-partners-kv.yaml dotnet8-8.0.100-8.0.0/src/arcade/.vault-config/dnceng-partners-kv.yaml --- dotnet8-8.0.100-8.0.0~rc2/src/arcade/.vault-config/dnceng-partners-kv.yaml 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/arcade/.vault-config/dnceng-partners-kv.yaml 2023-11-13 13:20:34.000000000 +0000 @@ -17,6 +17,12 @@ subscription: 11c6037b-227b-4d63-bee1-18c7b68c3a40 name: dotnetstagekeys + EngKeyVault: + type: azure-key-vault + parameters: + subscription: a4fc5514-21a9-4296-bfaf-5c7ee7fa35d1 + name: EngKeyVault + helixkv: type: azure-key-vault parameters: @@ -60,3 +66,13 @@ domainAccountSecret: name: dn-dependabot-account-redmond location: helixkv + + BotAccount-dotnet-bot-content-rw-grained-pat: + type: github-access-token + parameters: + gitHubBotAccountSecret: + name: BotAccount-dotnet-bot + location: EngKeyVault + gitHubBotAccountName: BotAccount-dotnet-build-bot + requiredScopes: content + description: "This pat is under the beta fine-grained tokens using dotnet as owner and repository specific for vscode-csharp and roslyn. The permissions are: Content - Read and write." diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/Aspire.sln dotnet8-8.0.100-8.0.0/src/aspire/Aspire.sln --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/Aspire.sln 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/Aspire.sln 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,456 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting", "src\Aspire.Hosting\Aspire.Hosting.csproj", "{B52DCF1A-465D-4E92-A68A-0EE1A9ED49DF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyFrontend", "samples\eShopLite\MyFrontend\MyFrontend.csproj", "{E958BE04-81C2-434C-9E6C-CA145A2B8218}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppHost", "samples\eShopLite\AppHost\AppHost.csproj", "{C1D595AD-FFFD-4E52-AAF6-8DD8C4BD67F1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{D173887B-AF42-4576-B9C1-96B9E9B3D9C0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceDefaults", "samples\eShopLite\ServiceDefaults\ServiceDefaults.csproj", "{C7B2309C-073A-4552-A508-A69768B64C6F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CatalogService", "samples\eShopLite\CatalogService\CatalogService.csproj", "{6D04BB34-1CC6-4FF3-A02A-1FFAC2A7A4F3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasketService", "samples\eShopLite\BasketService\BasketService.csproj", "{3FC74EA6-D554-4A87-AED5-A08FE407BBF4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiGateway", "samples\eShopLite\ApiGateway\ApiGateway.csproj", "{934625C7-243F-4659-9421-515301CF0263}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.StackExchange.Redis", "src\Components\Aspire.StackExchange.Redis\Aspire.StackExchange.Redis.csproj", "{7D4A7A84-B297-4777-9E2A-D8B1C427CAC7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Components", "Components", "{27381127-6C45-4B4C-8F18-41FF48DFE4B2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ServiceDiscovery.Abstractions", "src\Microsoft.Extensions.ServiceDiscovery.Abstractions\Microsoft.Extensions.ServiceDiscovery.Abstractions.csproj", "{145E569B-633F-4FF0-B273-CB1D3B49B9F2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ServiceDiscovery", "src\Microsoft.Extensions.ServiceDiscovery\Microsoft.Extensions.ServiceDiscovery.csproj", "{7570F683-EB48-467F-B1B1-70FF0153F52B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ServiceDiscovery.Dns", "src\Microsoft.Extensions.ServiceDiscovery.Dns\Microsoft.Extensions.ServiceDiscovery.Dns.csproj", "{327CB76F-3BB8-4955-A01D-CD8553AD36CC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ServiceDiscovery", "ServiceDiscovery", "{63BB22D3-0FAC-4E87-BF9D-9FA2441684C9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosting", "Hosting", "{B80354C7-BE58-43F6-8928-9F3A74AB7F47}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.Azure", "src\Aspire.Hosting.Azure\Aspire.Hosting.Azure.csproj", "{B38EBDC8-9255-4AC2-A10E-5C908D4BE862}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{4981B3A5-4AFD-4191-BF7D-8692D9783D60}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Npgsql.EntityFrameworkCore.PostgreSQL.Tests", "tests\Aspire.Npgsql.EntityFrameworkCore.PostgreSQL.Tests\Aspire.Npgsql.EntityFrameworkCore.PostgreSQL.Tests.csproj", "{5A7D4F1C-98CB-45D5-955E-A601CFA70884}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Azure.Messaging.ServiceBus", "src\Components\Aspire.Azure.Messaging.ServiceBus\Aspire.Azure.Messaging.ServiceBus.csproj", "{CC9CD371-9909-4101-83F1-22C7C2617DE4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrderProcessor", "samples\eShopLite\OrderProcessor\OrderProcessor.csproj", "{955EDC6D-11A3-4408-9C00-7BFE140AD597}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Npgsql.EntityFrameworkCore.PostgreSQL", "src\Components\Aspire.Npgsql.EntityFrameworkCore.PostgreSQL\Aspire.Npgsql.EntityFrameworkCore.PostgreSQL.csproj", "{0F505BE9-42FE-49EC-93CC-57584C4C9671}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Microsoft.Data.SqlClient", "src\Components\Aspire.Microsoft.Data.SqlClient\Aspire.Microsoft.Data.SqlClient.csproj", "{47714F18-6CEC-4F61-92BC-BAE60C619F61}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Microsoft.EntityFrameworkCore.SqlServer", "src\Components\Aspire.Microsoft.EntityFrameworkCore.SqlServer\Aspire.Microsoft.EntityFrameworkCore.SqlServer.csproj", "{39AD3DBA-6B79-44FC-9637-232544D0CA46}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Azure.Security.KeyVault", "src\Components\Aspire.Azure.Security.KeyVault\Aspire.Azure.Security.KeyVault.csproj", "{0C97F423-DAAE-4B63-B1B1-CB85E2840B98}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Azure.Security.KeyVault.Tests", "tests\Aspire.Azure.Security.KeyVault.Tests\Aspire.Azure.Security.KeyVault.Tests.csproj", "{23075FC1-D893-434D-9A20-02870B96ABA7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Components.Common.Tests", "tests\Aspire.Components.Common.Tests\Aspire.Components.Common.Tests.csproj", "{F371C3EE-8AB3-4261-A5F2-9A1FF77ADC19}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.StackExchange.Redis.Tests", "tests\Aspire.StackExchange.Redis.Tests\Aspire.StackExchange.Redis.Tests.csproj", "{6E7B2D62-A1A4-4232-8FA9-DB01A454B9C1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Azure.Messaging.ServiceBus.Tests", "tests\Aspire.Azure.Messaging.ServiceBus.Tests\Aspire.Azure.Messaging.ServiceBus.Tests.csproj", "{26E8EA4C-D3A6-4C6A-900D-2AA2C80D0AAF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ServiceDiscovery", "ServiceDiscovery", "{7C4F0F84-BAEC-469D-A3BC-28209B2C11AC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ServiceDiscovery.Tests", "tests\Microsoft.Extensions.ServiceDiscovery.Tests\Microsoft.Extensions.ServiceDiscovery.Tests.csproj", "{CD7BF1EC-ABF9-48DE-8683-353216DB4958}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ServiceDiscovery.Dns.Tests", "tests\Microsoft.Extensions.ServiceDiscovery.Dns.Tests\Microsoft.Extensions.ServiceDiscovery.Dns.Tests.csproj", "{845B39FE-A982-44EB-A4DB-CCCD93CBF261}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.ProjectTemplates", "src\Aspire.ProjectTemplates\Aspire.ProjectTemplates.csproj", "{29012D95-AA39-4DC9-AFAA-2FC3A8CDEF33}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Npgsql", "src\Components\Aspire.Npgsql\Aspire.Npgsql.csproj", "{AC5970BF-7A71-4CFE-BC21-8E597ECD347C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Dashboard", "src\Aspire.Dashboard\Aspire.Dashboard.csproj", "{D403DB4A-ABFC-4B13-A920-C6AC9B3EEE0B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Azure.Data.Tables", "src\Components\Aspire.Azure.Data.Tables\Aspire.Azure.Data.Tables.csproj", "{88A89C15-11DF-4F32-AA61-6896AF09F470}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Azure.Data.Tables.Tests", "tests\Aspire.Azure.Data.Tables.Tests\Aspire.Azure.Data.Tables.Tests.csproj", "{06D5A9E7-8CAA-4F0F-A41E-8C97D2FF8542}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Azure.Storage.Blobs", "src\Components\Aspire.Azure.Storage.Blobs\Aspire.Azure.Storage.Blobs.csproj", "{2E1E217C-A836-49E3-B655-6E42AD3E7943}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Azure.Storage.Blobs.Tests", "tests\Aspire.Azure.Storage.Blobs.Tests\Aspire.Azure.Storage.Blobs.Tests.csproj", "{59B8C4DE-FB94-4670-9DDE-B04F461219CB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Azure.Storage.Queues", "src\Components\Aspire.Azure.Storage.Queues\Aspire.Azure.Storage.Queues.csproj", "{FFFD251E-F555-4008-A759-5727784B0C98}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Azure.Storage.Queues.Tests", "tests\Aspire.Azure.Storage.Queues.Tests\Aspire.Azure.Storage.Queues.Tests.csproj", "{E0E0D5C0-E8D1-458E-9C3A-F0B858BBAD3B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ServiceDiscovery.Yarp", "src\Microsoft.Extensions.ServiceDiscovery.Yarp\Microsoft.Extensions.ServiceDiscovery.Yarp.csproj", "{EEED6AD9-6FC3-4483-B44E-691FF2DECFAF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.StackExchange.Redis.OutputCaching", "src\Components\Aspire.StackExchange.Redis.OutputCaching\Aspire.StackExchange.Redis.OutputCaching.csproj", "{A3262B32-E4F6-4F95-9BB9-D522F798C0D3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.StackExchange.Redis.OutputCaching.Tests", "tests\Aspire.StackExchange.Redis.OutputCaching.Tests\Aspire.StackExchange.Redis.OutputCaching.Tests.csproj", "{B130B8E2-BF6E-4541-856F-F51558F8050A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.StackExchange.Redis.DistributedCaching", "src\Components\Aspire.StackExchange.Redis.DistributedCaching\Aspire.StackExchange.Redis.DistributedCaching.csproj", "{1CB79745-8A58-4AE7-B6C8-2362DCDB1CA6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.StackExchange.Redis.DistributedCaching.Tests", "tests\Aspire.StackExchange.Redis.DistributedCaching.Tests\Aspire.StackExchange.Redis.DistributedCaching.Tests.csproj", "{56D1697B-8CF9-4994-A484-060878821F9D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.Tests", "tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj", "{D3B3AFFF-211A-4450-AE5E-055BD1323D2E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestProject.AppHost", "tests\testproject\TestProject.AppHost\TestProject.AppHost.csproj", "{C3BA722D-B03F-465B-BC47-421CA97B97E0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testproject", "testproject", "{975F6F41-B455-451D-A312-098DE4A167B6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestProject.ServiceA", "tests\testproject\TestProject.ServiceA\TestProject.ServiceA.csproj", "{31F5E4F3-AC4E-4538-BC7D-85BCF9CA686A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestProject.ServiceB", "tests\testproject\TestProject.ServiceB\TestProject.ServiceB.csproj", "{A37AAFDB-545B-4599-806A-EFCB8B310446}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestProject.ServiceC", "tests\testproject\TestProject.ServiceC\TestProject.ServiceC.csproj", "{8CB12764-E469-4BB5-8554-5F9CA0F6DE18}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Dashboard.Tests", "tests\Aspire.Dashboard.Tests\Aspire.Dashboard.Tests.csproj", "{1ABCD945-5CAA-4F30-A741-7A9DA919254A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workload", "Workload", "{F534D4F8-5E3A-42FC-BCD7-4C2D6060F9C8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.Aspire", "src\Microsoft.NET.Sdk.Aspire\Microsoft.NET.Sdk.Aspire.csproj", "{8C6877C0-3CB2-4B99-B8BD-1B7D0CADB543}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.Sdk", "src\Aspire.Hosting.Sdk\Aspire.Hosting.Sdk.csproj", "{E20280B8-8BE0-4967-AFC2-65FFCD6EC5E4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Npgsql.Tests", "tests\Aspire.Npgsql.Tests\Aspire.Npgsql.Tests.csproj", "{6EAA089D-7ADD-4C74-B040-FD3D75DB5C75}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Microsoft.Data.SqlClient.Tests", "tests\Aspire.Microsoft.Data.SqlClient.Tests\Aspire.Microsoft.Data.SqlClient.Tests.csproj", "{9D9C360B-9DF1-4076-8416-66964427C8F3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Microsoft.EntityFrameworkCore.SqlServer.Tests", "tests\Aspire.Microsoft.EntityFrameworkCore.SqlServer.Tests\Aspire.Microsoft.EntityFrameworkCore.SqlServer.Tests.csproj", "{67B9A8E4-7F51-4419-85BC-4BB0C2E67E71}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dapr", "dapr", "{57A42144-739E-49A7-BADB-BB8F5F20FA17}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DaprServiceA", "samples\dapr\ServiceA\DaprServiceA.csproj", "{185BCD98-BD26-4535-BE6E-44AAA4F7C145}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DaprServiceB", "samples\dapr\ServiceB\DaprServiceB.csproj", "{B1D88C56-46EB-4171-A997-56E67875CF80}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DaprAppHost", "samples\dapr\AppHost\DaprAppHost.csproj", "{8EB719EE-38E4-4FAD-B3E5-F71FA7F7A3EB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.Dapr", "src\Aspire.Hosting.Dapr\Aspire.Hosting.Dapr.csproj", "{E2EC79D0-80F7-4471-9613-D7C8C3D52F95}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.Azure.Provisioning", "src\Aspire.Hosting.Azure.Provisioning\Aspire.Hosting.Azure.Provisioning.csproj", "{D4BD974F-6505-43FC-A94E-2019F0DB5D5D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eShopLite", "eShopLite", "{A68BA1A5-1604-433D-9778-DC0199831C2A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CatalogDb", "samples\eShopLite\CatalogDb\CatalogDb.csproj", "{A84C4EE3-2601-4804-BCDC-E9948E164A22}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{991DB378-6CB5-4441-BFC3-657400690FC3}" + ProjectSection(SolutionItems) = preProject + Directory.Packages.props = Directory.Packages.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.RabbitMQ.Client", "src\Components\Aspire.RabbitMQ.Client\Aspire.RabbitMQ.Client.csproj", "{4D8A92AB-4E77-4965-AD8E-8E206DCE66A4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.RabbitMQ.Client.Tests", "tests\Aspire.RabbitMQ.Client.Tests\Aspire.RabbitMQ.Client.Tests.csproj", "{165411FE-755E-4869-A756-F87F455860AC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B52DCF1A-465D-4E92-A68A-0EE1A9ED49DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B52DCF1A-465D-4E92-A68A-0EE1A9ED49DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B52DCF1A-465D-4E92-A68A-0EE1A9ED49DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B52DCF1A-465D-4E92-A68A-0EE1A9ED49DF}.Release|Any CPU.Build.0 = Release|Any CPU + {E958BE04-81C2-434C-9E6C-CA145A2B8218}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E958BE04-81C2-434C-9E6C-CA145A2B8218}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E958BE04-81C2-434C-9E6C-CA145A2B8218}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E958BE04-81C2-434C-9E6C-CA145A2B8218}.Release|Any CPU.Build.0 = Release|Any CPU + {C1D595AD-FFFD-4E52-AAF6-8DD8C4BD67F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1D595AD-FFFD-4E52-AAF6-8DD8C4BD67F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1D595AD-FFFD-4E52-AAF6-8DD8C4BD67F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1D595AD-FFFD-4E52-AAF6-8DD8C4BD67F1}.Release|Any CPU.Build.0 = Release|Any CPU + {C7B2309C-073A-4552-A508-A69768B64C6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7B2309C-073A-4552-A508-A69768B64C6F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7B2309C-073A-4552-A508-A69768B64C6F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7B2309C-073A-4552-A508-A69768B64C6F}.Release|Any CPU.Build.0 = Release|Any CPU + {6D04BB34-1CC6-4FF3-A02A-1FFAC2A7A4F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D04BB34-1CC6-4FF3-A02A-1FFAC2A7A4F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D04BB34-1CC6-4FF3-A02A-1FFAC2A7A4F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D04BB34-1CC6-4FF3-A02A-1FFAC2A7A4F3}.Release|Any CPU.Build.0 = Release|Any CPU + {3FC74EA6-D554-4A87-AED5-A08FE407BBF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3FC74EA6-D554-4A87-AED5-A08FE407BBF4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3FC74EA6-D554-4A87-AED5-A08FE407BBF4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3FC74EA6-D554-4A87-AED5-A08FE407BBF4}.Release|Any CPU.Build.0 = Release|Any CPU + {934625C7-243F-4659-9421-515301CF0263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {934625C7-243F-4659-9421-515301CF0263}.Debug|Any CPU.Build.0 = Debug|Any CPU + {934625C7-243F-4659-9421-515301CF0263}.Release|Any CPU.ActiveCfg = Release|Any CPU + {934625C7-243F-4659-9421-515301CF0263}.Release|Any CPU.Build.0 = Release|Any CPU + {7D4A7A84-B297-4777-9E2A-D8B1C427CAC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D4A7A84-B297-4777-9E2A-D8B1C427CAC7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D4A7A84-B297-4777-9E2A-D8B1C427CAC7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D4A7A84-B297-4777-9E2A-D8B1C427CAC7}.Release|Any CPU.Build.0 = Release|Any CPU + {145E569B-633F-4FF0-B273-CB1D3B49B9F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {145E569B-633F-4FF0-B273-CB1D3B49B9F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {145E569B-633F-4FF0-B273-CB1D3B49B9F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {145E569B-633F-4FF0-B273-CB1D3B49B9F2}.Release|Any CPU.Build.0 = Release|Any CPU + {7570F683-EB48-467F-B1B1-70FF0153F52B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7570F683-EB48-467F-B1B1-70FF0153F52B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7570F683-EB48-467F-B1B1-70FF0153F52B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7570F683-EB48-467F-B1B1-70FF0153F52B}.Release|Any CPU.Build.0 = Release|Any CPU + {327CB76F-3BB8-4955-A01D-CD8553AD36CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {327CB76F-3BB8-4955-A01D-CD8553AD36CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {327CB76F-3BB8-4955-A01D-CD8553AD36CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {327CB76F-3BB8-4955-A01D-CD8553AD36CC}.Release|Any CPU.Build.0 = Release|Any CPU + {B38EBDC8-9255-4AC2-A10E-5C908D4BE862}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B38EBDC8-9255-4AC2-A10E-5C908D4BE862}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B38EBDC8-9255-4AC2-A10E-5C908D4BE862}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B38EBDC8-9255-4AC2-A10E-5C908D4BE862}.Release|Any CPU.Build.0 = Release|Any CPU + {5A7D4F1C-98CB-45D5-955E-A601CFA70884}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A7D4F1C-98CB-45D5-955E-A601CFA70884}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A7D4F1C-98CB-45D5-955E-A601CFA70884}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A7D4F1C-98CB-45D5-955E-A601CFA70884}.Release|Any CPU.Build.0 = Release|Any CPU + {CC9CD371-9909-4101-83F1-22C7C2617DE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC9CD371-9909-4101-83F1-22C7C2617DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC9CD371-9909-4101-83F1-22C7C2617DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC9CD371-9909-4101-83F1-22C7C2617DE4}.Release|Any CPU.Build.0 = Release|Any CPU + {955EDC6D-11A3-4408-9C00-7BFE140AD597}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {955EDC6D-11A3-4408-9C00-7BFE140AD597}.Debug|Any CPU.Build.0 = Debug|Any CPU + {955EDC6D-11A3-4408-9C00-7BFE140AD597}.Release|Any CPU.ActiveCfg = Release|Any CPU + {955EDC6D-11A3-4408-9C00-7BFE140AD597}.Release|Any CPU.Build.0 = Release|Any CPU + {0F505BE9-42FE-49EC-93CC-57584C4C9671}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F505BE9-42FE-49EC-93CC-57584C4C9671}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F505BE9-42FE-49EC-93CC-57584C4C9671}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F505BE9-42FE-49EC-93CC-57584C4C9671}.Release|Any CPU.Build.0 = Release|Any CPU + {47714F18-6CEC-4F61-92BC-BAE60C619F61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47714F18-6CEC-4F61-92BC-BAE60C619F61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47714F18-6CEC-4F61-92BC-BAE60C619F61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47714F18-6CEC-4F61-92BC-BAE60C619F61}.Release|Any CPU.Build.0 = Release|Any CPU + {39AD3DBA-6B79-44FC-9637-232544D0CA46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39AD3DBA-6B79-44FC-9637-232544D0CA46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39AD3DBA-6B79-44FC-9637-232544D0CA46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39AD3DBA-6B79-44FC-9637-232544D0CA46}.Release|Any CPU.Build.0 = Release|Any CPU + {0C97F423-DAAE-4B63-B1B1-CB85E2840B98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C97F423-DAAE-4B63-B1B1-CB85E2840B98}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C97F423-DAAE-4B63-B1B1-CB85E2840B98}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C97F423-DAAE-4B63-B1B1-CB85E2840B98}.Release|Any CPU.Build.0 = Release|Any CPU + {23075FC1-D893-434D-9A20-02870B96ABA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23075FC1-D893-434D-9A20-02870B96ABA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23075FC1-D893-434D-9A20-02870B96ABA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23075FC1-D893-434D-9A20-02870B96ABA7}.Release|Any CPU.Build.0 = Release|Any CPU + {F371C3EE-8AB3-4261-A5F2-9A1FF77ADC19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F371C3EE-8AB3-4261-A5F2-9A1FF77ADC19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F371C3EE-8AB3-4261-A5F2-9A1FF77ADC19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F371C3EE-8AB3-4261-A5F2-9A1FF77ADC19}.Release|Any CPU.Build.0 = Release|Any CPU + {6E7B2D62-A1A4-4232-8FA9-DB01A454B9C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E7B2D62-A1A4-4232-8FA9-DB01A454B9C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E7B2D62-A1A4-4232-8FA9-DB01A454B9C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E7B2D62-A1A4-4232-8FA9-DB01A454B9C1}.Release|Any CPU.Build.0 = Release|Any CPU + {26E8EA4C-D3A6-4C6A-900D-2AA2C80D0AAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26E8EA4C-D3A6-4C6A-900D-2AA2C80D0AAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26E8EA4C-D3A6-4C6A-900D-2AA2C80D0AAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26E8EA4C-D3A6-4C6A-900D-2AA2C80D0AAF}.Release|Any CPU.Build.0 = Release|Any CPU + {CD7BF1EC-ABF9-48DE-8683-353216DB4958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD7BF1EC-ABF9-48DE-8683-353216DB4958}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD7BF1EC-ABF9-48DE-8683-353216DB4958}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD7BF1EC-ABF9-48DE-8683-353216DB4958}.Release|Any CPU.Build.0 = Release|Any CPU + {845B39FE-A982-44EB-A4DB-CCCD93CBF261}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {845B39FE-A982-44EB-A4DB-CCCD93CBF261}.Debug|Any CPU.Build.0 = Debug|Any CPU + {845B39FE-A982-44EB-A4DB-CCCD93CBF261}.Release|Any CPU.ActiveCfg = Release|Any CPU + {845B39FE-A982-44EB-A4DB-CCCD93CBF261}.Release|Any CPU.Build.0 = Release|Any CPU + {29012D95-AA39-4DC9-AFAA-2FC3A8CDEF33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29012D95-AA39-4DC9-AFAA-2FC3A8CDEF33}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29012D95-AA39-4DC9-AFAA-2FC3A8CDEF33}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29012D95-AA39-4DC9-AFAA-2FC3A8CDEF33}.Release|Any CPU.Build.0 = Release|Any CPU + {AC5970BF-7A71-4CFE-BC21-8E597ECD347C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC5970BF-7A71-4CFE-BC21-8E597ECD347C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC5970BF-7A71-4CFE-BC21-8E597ECD347C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC5970BF-7A71-4CFE-BC21-8E597ECD347C}.Release|Any CPU.Build.0 = Release|Any CPU + {D403DB4A-ABFC-4B13-A920-C6AC9B3EEE0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D403DB4A-ABFC-4B13-A920-C6AC9B3EEE0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D403DB4A-ABFC-4B13-A920-C6AC9B3EEE0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D403DB4A-ABFC-4B13-A920-C6AC9B3EEE0B}.Release|Any CPU.Build.0 = Release|Any CPU + {88A89C15-11DF-4F32-AA61-6896AF09F470}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88A89C15-11DF-4F32-AA61-6896AF09F470}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88A89C15-11DF-4F32-AA61-6896AF09F470}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88A89C15-11DF-4F32-AA61-6896AF09F470}.Release|Any CPU.Build.0 = Release|Any CPU + {06D5A9E7-8CAA-4F0F-A41E-8C97D2FF8542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06D5A9E7-8CAA-4F0F-A41E-8C97D2FF8542}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06D5A9E7-8CAA-4F0F-A41E-8C97D2FF8542}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06D5A9E7-8CAA-4F0F-A41E-8C97D2FF8542}.Release|Any CPU.Build.0 = Release|Any CPU + {2E1E217C-A836-49E3-B655-6E42AD3E7943}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E1E217C-A836-49E3-B655-6E42AD3E7943}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E1E217C-A836-49E3-B655-6E42AD3E7943}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E1E217C-A836-49E3-B655-6E42AD3E7943}.Release|Any CPU.Build.0 = Release|Any CPU + {59B8C4DE-FB94-4670-9DDE-B04F461219CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59B8C4DE-FB94-4670-9DDE-B04F461219CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59B8C4DE-FB94-4670-9DDE-B04F461219CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59B8C4DE-FB94-4670-9DDE-B04F461219CB}.Release|Any CPU.Build.0 = Release|Any CPU + {FFFD251E-F555-4008-A759-5727784B0C98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFFD251E-F555-4008-A759-5727784B0C98}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFFD251E-F555-4008-A759-5727784B0C98}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFFD251E-F555-4008-A759-5727784B0C98}.Release|Any CPU.Build.0 = Release|Any CPU + {E0E0D5C0-E8D1-458E-9C3A-F0B858BBAD3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0E0D5C0-E8D1-458E-9C3A-F0B858BBAD3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0E0D5C0-E8D1-458E-9C3A-F0B858BBAD3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0E0D5C0-E8D1-458E-9C3A-F0B858BBAD3B}.Release|Any CPU.Build.0 = Release|Any CPU + {EEED6AD9-6FC3-4483-B44E-691FF2DECFAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EEED6AD9-6FC3-4483-B44E-691FF2DECFAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EEED6AD9-6FC3-4483-B44E-691FF2DECFAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EEED6AD9-6FC3-4483-B44E-691FF2DECFAF}.Release|Any CPU.Build.0 = Release|Any CPU + {A3262B32-E4F6-4F95-9BB9-D522F798C0D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3262B32-E4F6-4F95-9BB9-D522F798C0D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3262B32-E4F6-4F95-9BB9-D522F798C0D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3262B32-E4F6-4F95-9BB9-D522F798C0D3}.Release|Any CPU.Build.0 = Release|Any CPU + {B130B8E2-BF6E-4541-856F-F51558F8050A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B130B8E2-BF6E-4541-856F-F51558F8050A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B130B8E2-BF6E-4541-856F-F51558F8050A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B130B8E2-BF6E-4541-856F-F51558F8050A}.Release|Any CPU.Build.0 = Release|Any CPU + {1CB79745-8A58-4AE7-B6C8-2362DCDB1CA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CB79745-8A58-4AE7-B6C8-2362DCDB1CA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CB79745-8A58-4AE7-B6C8-2362DCDB1CA6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CB79745-8A58-4AE7-B6C8-2362DCDB1CA6}.Release|Any CPU.Build.0 = Release|Any CPU + {56D1697B-8CF9-4994-A484-060878821F9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56D1697B-8CF9-4994-A484-060878821F9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56D1697B-8CF9-4994-A484-060878821F9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56D1697B-8CF9-4994-A484-060878821F9D}.Release|Any CPU.Build.0 = Release|Any CPU + {D3B3AFFF-211A-4450-AE5E-055BD1323D2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3B3AFFF-211A-4450-AE5E-055BD1323D2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3B3AFFF-211A-4450-AE5E-055BD1323D2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3B3AFFF-211A-4450-AE5E-055BD1323D2E}.Release|Any CPU.Build.0 = Release|Any CPU + {C3BA722D-B03F-465B-BC47-421CA97B97E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3BA722D-B03F-465B-BC47-421CA97B97E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3BA722D-B03F-465B-BC47-421CA97B97E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3BA722D-B03F-465B-BC47-421CA97B97E0}.Release|Any CPU.Build.0 = Release|Any CPU + {31F5E4F3-AC4E-4538-BC7D-85BCF9CA686A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31F5E4F3-AC4E-4538-BC7D-85BCF9CA686A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31F5E4F3-AC4E-4538-BC7D-85BCF9CA686A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31F5E4F3-AC4E-4538-BC7D-85BCF9CA686A}.Release|Any CPU.Build.0 = Release|Any CPU + {A37AAFDB-545B-4599-806A-EFCB8B310446}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A37AAFDB-545B-4599-806A-EFCB8B310446}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A37AAFDB-545B-4599-806A-EFCB8B310446}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A37AAFDB-545B-4599-806A-EFCB8B310446}.Release|Any CPU.Build.0 = Release|Any CPU + {8CB12764-E469-4BB5-8554-5F9CA0F6DE18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CB12764-E469-4BB5-8554-5F9CA0F6DE18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CB12764-E469-4BB5-8554-5F9CA0F6DE18}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CB12764-E469-4BB5-8554-5F9CA0F6DE18}.Release|Any CPU.Build.0 = Release|Any CPU + {1ABCD945-5CAA-4F30-A741-7A9DA919254A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1ABCD945-5CAA-4F30-A741-7A9DA919254A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1ABCD945-5CAA-4F30-A741-7A9DA919254A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1ABCD945-5CAA-4F30-A741-7A9DA919254A}.Release|Any CPU.Build.0 = Release|Any CPU + {8C6877C0-3CB2-4B99-B8BD-1B7D0CADB543}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C6877C0-3CB2-4B99-B8BD-1B7D0CADB543}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C6877C0-3CB2-4B99-B8BD-1B7D0CADB543}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C6877C0-3CB2-4B99-B8BD-1B7D0CADB543}.Release|Any CPU.Build.0 = Release|Any CPU + {E20280B8-8BE0-4967-AFC2-65FFCD6EC5E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E20280B8-8BE0-4967-AFC2-65FFCD6EC5E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E20280B8-8BE0-4967-AFC2-65FFCD6EC5E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E20280B8-8BE0-4967-AFC2-65FFCD6EC5E4}.Release|Any CPU.Build.0 = Release|Any CPU + {6EAA089D-7ADD-4C74-B040-FD3D75DB5C75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6EAA089D-7ADD-4C74-B040-FD3D75DB5C75}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6EAA089D-7ADD-4C74-B040-FD3D75DB5C75}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6EAA089D-7ADD-4C74-B040-FD3D75DB5C75}.Release|Any CPU.Build.0 = Release|Any CPU + {9D9C360B-9DF1-4076-8416-66964427C8F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D9C360B-9DF1-4076-8416-66964427C8F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D9C360B-9DF1-4076-8416-66964427C8F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D9C360B-9DF1-4076-8416-66964427C8F3}.Release|Any CPU.Build.0 = Release|Any CPU + {67B9A8E4-7F51-4419-85BC-4BB0C2E67E71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67B9A8E4-7F51-4419-85BC-4BB0C2E67E71}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67B9A8E4-7F51-4419-85BC-4BB0C2E67E71}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67B9A8E4-7F51-4419-85BC-4BB0C2E67E71}.Release|Any CPU.Build.0 = Release|Any CPU + {185BCD98-BD26-4535-BE6E-44AAA4F7C145}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {185BCD98-BD26-4535-BE6E-44AAA4F7C145}.Debug|Any CPU.Build.0 = Debug|Any CPU + {185BCD98-BD26-4535-BE6E-44AAA4F7C145}.Release|Any CPU.ActiveCfg = Release|Any CPU + {185BCD98-BD26-4535-BE6E-44AAA4F7C145}.Release|Any CPU.Build.0 = Release|Any CPU + {B1D88C56-46EB-4171-A997-56E67875CF80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1D88C56-46EB-4171-A997-56E67875CF80}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1D88C56-46EB-4171-A997-56E67875CF80}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1D88C56-46EB-4171-A997-56E67875CF80}.Release|Any CPU.Build.0 = Release|Any CPU + {8EB719EE-38E4-4FAD-B3E5-F71FA7F7A3EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8EB719EE-38E4-4FAD-B3E5-F71FA7F7A3EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8EB719EE-38E4-4FAD-B3E5-F71FA7F7A3EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8EB719EE-38E4-4FAD-B3E5-F71FA7F7A3EB}.Release|Any CPU.Build.0 = Release|Any CPU + {E2EC79D0-80F7-4471-9613-D7C8C3D52F95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2EC79D0-80F7-4471-9613-D7C8C3D52F95}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2EC79D0-80F7-4471-9613-D7C8C3D52F95}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2EC79D0-80F7-4471-9613-D7C8C3D52F95}.Release|Any CPU.Build.0 = Release|Any CPU + {D4BD974F-6505-43FC-A94E-2019F0DB5D5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4BD974F-6505-43FC-A94E-2019F0DB5D5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4BD974F-6505-43FC-A94E-2019F0DB5D5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4BD974F-6505-43FC-A94E-2019F0DB5D5D}.Release|Any CPU.Build.0 = Release|Any CPU + {A84C4EE3-2601-4804-BCDC-E9948E164A22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A84C4EE3-2601-4804-BCDC-E9948E164A22}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A84C4EE3-2601-4804-BCDC-E9948E164A22}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A84C4EE3-2601-4804-BCDC-E9948E164A22}.Release|Any CPU.Build.0 = Release|Any CPU + {4D8A92AB-4E77-4965-AD8E-8E206DCE66A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D8A92AB-4E77-4965-AD8E-8E206DCE66A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D8A92AB-4E77-4965-AD8E-8E206DCE66A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D8A92AB-4E77-4965-AD8E-8E206DCE66A4}.Release|Any CPU.Build.0 = Release|Any CPU + {165411FE-755E-4869-A756-F87F455860AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {165411FE-755E-4869-A756-F87F455860AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {165411FE-755E-4869-A756-F87F455860AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {165411FE-755E-4869-A756-F87F455860AC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {B52DCF1A-465D-4E92-A68A-0EE1A9ED49DF} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47} + {E958BE04-81C2-434C-9E6C-CA145A2B8218} = {A68BA1A5-1604-433D-9778-DC0199831C2A} + {C1D595AD-FFFD-4E52-AAF6-8DD8C4BD67F1} = {A68BA1A5-1604-433D-9778-DC0199831C2A} + {C7B2309C-073A-4552-A508-A69768B64C6F} = {A68BA1A5-1604-433D-9778-DC0199831C2A} + {6D04BB34-1CC6-4FF3-A02A-1FFAC2A7A4F3} = {A68BA1A5-1604-433D-9778-DC0199831C2A} + {3FC74EA6-D554-4A87-AED5-A08FE407BBF4} = {A68BA1A5-1604-433D-9778-DC0199831C2A} + {934625C7-243F-4659-9421-515301CF0263} = {A68BA1A5-1604-433D-9778-DC0199831C2A} + {7D4A7A84-B297-4777-9E2A-D8B1C427CAC7} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} + {145E569B-633F-4FF0-B273-CB1D3B49B9F2} = {63BB22D3-0FAC-4E87-BF9D-9FA2441684C9} + {7570F683-EB48-467F-B1B1-70FF0153F52B} = {63BB22D3-0FAC-4E87-BF9D-9FA2441684C9} + {327CB76F-3BB8-4955-A01D-CD8553AD36CC} = {63BB22D3-0FAC-4E87-BF9D-9FA2441684C9} + {B38EBDC8-9255-4AC2-A10E-5C908D4BE862} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47} + {5A7D4F1C-98CB-45D5-955E-A601CFA70884} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {CC9CD371-9909-4101-83F1-22C7C2617DE4} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} + {955EDC6D-11A3-4408-9C00-7BFE140AD597} = {A68BA1A5-1604-433D-9778-DC0199831C2A} + {0F505BE9-42FE-49EC-93CC-57584C4C9671} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} + {47714F18-6CEC-4F61-92BC-BAE60C619F61} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} + {39AD3DBA-6B79-44FC-9637-232544D0CA46} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} + {0C97F423-DAAE-4B63-B1B1-CB85E2840B98} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} + {23075FC1-D893-434D-9A20-02870B96ABA7} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {F371C3EE-8AB3-4261-A5F2-9A1FF77ADC19} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {6E7B2D62-A1A4-4232-8FA9-DB01A454B9C1} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {26E8EA4C-D3A6-4C6A-900D-2AA2C80D0AAF} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {7C4F0F84-BAEC-469D-A3BC-28209B2C11AC} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {CD7BF1EC-ABF9-48DE-8683-353216DB4958} = {7C4F0F84-BAEC-469D-A3BC-28209B2C11AC} + {845B39FE-A982-44EB-A4DB-CCCD93CBF261} = {7C4F0F84-BAEC-469D-A3BC-28209B2C11AC} + {29012D95-AA39-4DC9-AFAA-2FC3A8CDEF33} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47} + {AC5970BF-7A71-4CFE-BC21-8E597ECD347C} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} + {D403DB4A-ABFC-4B13-A920-C6AC9B3EEE0B} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47} + {88A89C15-11DF-4F32-AA61-6896AF09F470} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} + {06D5A9E7-8CAA-4F0F-A41E-8C97D2FF8542} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {2E1E217C-A836-49E3-B655-6E42AD3E7943} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} + {59B8C4DE-FB94-4670-9DDE-B04F461219CB} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {FFFD251E-F555-4008-A759-5727784B0C98} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} + {E0E0D5C0-E8D1-458E-9C3A-F0B858BBAD3B} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {EEED6AD9-6FC3-4483-B44E-691FF2DECFAF} = {63BB22D3-0FAC-4E87-BF9D-9FA2441684C9} + {A3262B32-E4F6-4F95-9BB9-D522F798C0D3} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} + {B130B8E2-BF6E-4541-856F-F51558F8050A} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {1CB79745-8A58-4AE7-B6C8-2362DCDB1CA6} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} + {56D1697B-8CF9-4994-A484-060878821F9D} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {D3B3AFFF-211A-4450-AE5E-055BD1323D2E} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {C3BA722D-B03F-465B-BC47-421CA97B97E0} = {975F6F41-B455-451D-A312-098DE4A167B6} + {975F6F41-B455-451D-A312-098DE4A167B6} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {31F5E4F3-AC4E-4538-BC7D-85BCF9CA686A} = {975F6F41-B455-451D-A312-098DE4A167B6} + {A37AAFDB-545B-4599-806A-EFCB8B310446} = {975F6F41-B455-451D-A312-098DE4A167B6} + {8CB12764-E469-4BB5-8554-5F9CA0F6DE18} = {975F6F41-B455-451D-A312-098DE4A167B6} + {1ABCD945-5CAA-4F30-A741-7A9DA919254A} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {8C6877C0-3CB2-4B99-B8BD-1B7D0CADB543} = {F534D4F8-5E3A-42FC-BCD7-4C2D6060F9C8} + {E20280B8-8BE0-4967-AFC2-65FFCD6EC5E4} = {F534D4F8-5E3A-42FC-BCD7-4C2D6060F9C8} + {6EAA089D-7ADD-4C74-B040-FD3D75DB5C75} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {9D9C360B-9DF1-4076-8416-66964427C8F3} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {67B9A8E4-7F51-4419-85BC-4BB0C2E67E71} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {57A42144-739E-49A7-BADB-BB8F5F20FA17} = {D173887B-AF42-4576-B9C1-96B9E9B3D9C0} + {185BCD98-BD26-4535-BE6E-44AAA4F7C145} = {57A42144-739E-49A7-BADB-BB8F5F20FA17} + {B1D88C56-46EB-4171-A997-56E67875CF80} = {57A42144-739E-49A7-BADB-BB8F5F20FA17} + {8EB719EE-38E4-4FAD-B3E5-F71FA7F7A3EB} = {57A42144-739E-49A7-BADB-BB8F5F20FA17} + {E2EC79D0-80F7-4471-9613-D7C8C3D52F95} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47} + {D4BD974F-6505-43FC-A94E-2019F0DB5D5D} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47} + {A68BA1A5-1604-433D-9778-DC0199831C2A} = {D173887B-AF42-4576-B9C1-96B9E9B3D9C0} + {A84C4EE3-2601-4804-BCDC-E9948E164A22} = {A68BA1A5-1604-433D-9778-DC0199831C2A} + {4D8A92AB-4E77-4965-AD8E-8E206DCE66A4} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} + {165411FE-755E-4869-A756-F87F455860AC} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6DCEDFEC-988E-4CB3-B45B-191EB5086E0C} + EndGlobalSection +EndGlobal diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/build.cmd dotnet8-8.0.100-8.0.0/src/aspire/build.cmd --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/build.cmd 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/build.cmd 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +@echo off +setlocal + +set _args=%* +if "%~1"=="-?" set _args=-help +if "%~1"=="/?" set _args=-help + +powershell -ExecutionPolicy ByPass -NoProfile -Command "& '%~dp0eng\build.ps1'" %_args% +exit /b %ERRORLEVEL% \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/build.sh dotnet8-8.0.100-8.0.0/src/aspire/build.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/build.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/build.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +function is_cygwin_or_mingw() +{ + case $(uname -s) in + CYGWIN*) return 0;; + MINGW*) return 0;; + *) return 1;; + esac +} + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done + +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" +"$scriptroot/eng/build.sh" $@ \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/CODE_OF_CONDUCT.md dotnet8-8.0.100-8.0.0/src/aspire/CODE_OF_CONDUCT.md --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/CODE_OF_CONDUCT.md 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/CODE_OF_CONDUCT.md 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/CODEOWNERS dotnet8-8.0.100-8.0.0/src/aspire/CODEOWNERS --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/CODEOWNERS 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/CODEOWNERS 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1 @@ +/eng/SourceBuild* @dotnet/source-build-internal \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/.config/CredScanSuppressions.json dotnet8-8.0.100-8.0.0/src/aspire/.config/CredScanSuppressions.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/.config/CredScanSuppressions.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/.config/CredScanSuppressions.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,13 @@ +{ + "tool": "Credential Scanner", + "suppressions": [ + { + "placeholder": "postgres", + "_justification": "This is a default PostgreSQL username and password, used in integration tests to connect to local db instance." + }, + { + "placeholder": "fake", + "_justification": "This is fake key name, used in integration tests to try to connect to Azure ServiceBus." + } + ] +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/Directory.Build.props dotnet8-8.0.100-8.0.0/src/aspire/Directory.Build.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/Directory.Build.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/Directory.Build.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,43 @@ + + + + + + $(CopyrightNetFoundation) + preview + MIT + $(MSBuildThisFileDirectory)/src/Shared/ + $(SharedDir)Aspire_icon_256.png + false + true + true + true + true + embedded + true + Open + enable + enable + + $(RepositoryEngineeringDir).runsettings + + true + + + + + $(NoWarn);NU1507 + + + + + linux + darwin + windows + 386 + arm64 + amd64 + $(NuGetPackageRoot)microsoft.developercontrolplane.$(BuildOs)-$(BuildArch)/$(MicrosoftDeveloperControlPlanedarwinamd64PackageVersion)/tools/ + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/Directory.Build.targets dotnet8-8.0.100-8.0.0/src/aspire/Directory.Build.targets --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/Directory.Build.targets 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/Directory.Build.targets 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,18 @@ + + + + $(MSBuildProjectDirectory)\README.md + true + README.md + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/Directory.Packages.props dotnet8-8.0.100-8.0.0/src/aspire/Directory.Packages.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/Directory.Packages.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/Directory.Packages.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,103 @@ + + + true + 8.0.0-rtm.23511.16 + 8.0.0-rtm.23512.20 + 8.0.0-rtm.23512.13 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/docs/getting-perf-traces.md dotnet8-8.0.100-8.0.0/src/aspire/docs/getting-perf-traces.md --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/docs/getting-perf-traces.md 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/docs/getting-perf-traces.md 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,21 @@ +## Wall-clock time investigations + +### Collect the trace +Collect the trace using PerfView (https://github.com/microsoft/perfview/releases). Use Collect menu, then Collect again, to open the collection dialog and make the following changes from defaults: + +1. If you do not intend to share the trace with anyone, uncheck the "Zip" and "Merge" option. +1. Increase Circular MB to 8192. +1. Check the "Thread Time" checkbox. +1. Expand Advanced Options panel and make sure you have Kernel Base, Cpu Samples, File I/O, .NET, and Task (TPL) options checked. +1. In "Additional Providers" add `*Microsoft-Aspire-Hosting` (note the asterisk before the Aspire provider name!). + +Once you are ready, git "Start Collection" button and run your scenario. + +When done with the scenario, hit "Stop Collection". Wait for PerfView to finish merging and analyzing data (the "working" status bar stops flashing). + +### Verify that the trace contains data Aspire data + +This is an optional step, but if you are wondering if your trace has been captured properly, you can check the following: + +1. Open the trace (usually named PerfViewData.etl, if you haven't changed the name) and double click Events view. Verify you have a bunch of events from Microsoft-Aspire-Hosting provider. + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/docs/getting-started.md dotnet8-8.0.100-8.0.0/src/aspire/docs/getting-started.md --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/docs/getting-started.md 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/docs/getting-started.md 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,138 @@ +# Set up your environment + +Follow all steps in [machine-requirements](machine-requirements.md). + +## Add necessary NuGet feeds + +Add NuGet sources to apply the following feeds +- https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/v3/index.json +- https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json (https://github.com/dotnet/installer#installers-and-binaries) +- See [Install and manage packages in Visual Studio](https://learn.microsoft.com/nuget/consume-packages/install-use-packages-visual-studio#package-sources) for instructions. + +### Command line instructions +```sh +dotnet nuget add source --name dotnet-libraries-internal https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/v3/index.json +dotnet nuget add source --name dotnet8 https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json +``` + +## Install the Azure Artifacts Credential Provider for NuGet + +See [full setup instructions](https://github.com/microsoft/artifacts-credprovider#setup). + +### Installation on Windows + +#### Automatic PowerShell script + +[PowerShell helper script](https://github.com/microsoft/artifacts-credprovider/blob/master/helpers/installcredprovider.ps1) + +- To install netcore, run `installcredprovider.ps1` + - e.g. `iex "& { $(irm https://aka.ms/install-artifacts-credprovider.ps1) }"` +- To install both netfx and netcore, run `installcredprovider.ps1 -AddNetfx`. The netfx version is needed for nuget.exe. + - e.g. `iex "& { $(irm https://aka.ms/install-artifacts-credprovider.ps1) } -AddNetfx"` + +### Installation on Linux and Mac + +#### Automatic bash script + +[Linux or Mac helper script](https://github.com/microsoft/artifacts-credprovider/blob/master/helpers/installcredprovider.sh) + +Examples: +- `wget -qO- https://aka.ms/install-artifacts-credprovider.sh | bash` +- `sh -c "$(curl -fsSL https://aka.ms/install-artifacts-credprovider.sh)"` + +> Note: this script only installs the netcore version of the plugin. If you need to have it working with mono msbuild, you will need to download the version with both netcore and netfx binaries following the steps in [Manual installation on Linux and Mac](#installation-on-linux-and-mac) + +## Setup NuGet Feed + +Unless you work for Microsoft, you won't have access to the internal Azure +DevOps feed. We have a private mirror on GitHub you can use instead. To access +it, you need to perform the following steps: + +1. [Create a personal access token](https://github.com/settings/tokens/new) for + your GitHub account with the `read:packages` scope with your desired + expiration length: + [image](https://github.com/settings/tokens/new) + +1. At the command line, go to the root of the Aspire repo and run the following + commands to add the package feed to your NuGet configuration, replacing the + `` and `` placeholders with the relevant values: + ```text + dotnet nuget remove source dotnet-libraries-internal + dotnet nuget add source -u -p --name dotnet-libraries-internal "https://nuget.pkg.github.com/dotnet/index.json" + ``` + +## Install the Aspire dotnet workload + +### Visual Studio + +Ensure you are using the [Visual Studio 2022 Enterprise IntPreview Setup](https://aka.ms/vs/17/intpreview/vs_enterprise.exe) feed. + +Check the `ASP.NET and web development` Workload. + +Ensure the `.NET Aspire SDK` component is checked in `Individual components`. + +### Command line + +1. The RTM nightly SDK is aware that the Aspire workload exists, but the real manifest is not installed by default. In order to install it, you'll need to update the workload in a directory that has a NuGet.config[^3] with the right feeds configured[^2] so that it can pull the latest manifest. Once you have created the NuGet.config file in your working directory, then you need to run the following command[^1]: + + ```shell + dotnet workload update --skip-sign-check --interactive + ``` + +2. The above command will update the Aspire manifest in your SDK build, meaning it will already be setup for command-line and Visual Studio in-product acquisition (IPA) of the Aspire workload. In order to manually install the workload, you can run the following command[^1]: + + ```shell + dotnet workload install aspire --skip-sign-check --interactive + ``` + +[^1]: The `--skip-sign-check` flag is required because the packages we build out of the Aspire repo are not yet signed. +[^2]: If you want to create a separate NuGet.config instead, these are the contents you need: + ```xml + + + + + + + + + + + ``` +[^3]: If you don't want to create a NuGet.config file, you should also be able to run the `update` and `install` commands using the following extra argument: `--source https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/v3/index.json`. + +# Create a new Project + +## In Visual Studio + +1. Create a new Blazor Web App and check the "Enlist in Aspire orchestration" box +2. F5 +3. The dashboard should launch automatically. You can click on your app's "Endpoint" to navigate to the app. + +## Using command line using workload templates + +- To create an empty Aspire project[^3], run the following command:: + +```shell + dotnet new aspire +``` + +- To create an Aspire project using the Starter template, run the following command: + +```shell + dotnet new aspire-starter +``` + +[^3]: In order for these commands to work, you must have already installed the Aspire workload by following the steps in #Install-the-Aspire-dotnet-workload section. + +You need to create a `NuGet.config` file in the root directory of your project with the contents above. +Once that is created you can build with + +```shell + dotnet build +``` + +And then run with +```shell + dotnet run --project "$(basename $PWD).AppHost" +``` diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/docs/machine-requirements.md dotnet8-8.0.100-8.0.0/src/aspire/docs/machine-requirements.md --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/docs/machine-requirements.md 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/docs/machine-requirements.md 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,20 @@ +# Set up your environment + +## Install Visual Studio 2022 Internal Preview (dogfood) + +[Visual Studio 2022 Enterprise IntPreview Setup](https://aka.ms/vs/17/intpreview/vs_enterprise.exe) + - This channel updates nightly. You need a build from 12-Oct-2023 or later. + +## Install .NET 8 RTM nightly SDK + +Install the .NET 8 RTM nightly SDK version 8.0.100-rtm.23512.16 or newer: + 1. [Windows x64 link](https://dotnetbuilds.azureedge.net/public/Sdk/8.0.100-rtm.23512.16/dotnet-sdk-8.0.100-rtm.23512.16-win-x64.exe) + 2. [Linux x64 link](https://dotnetbuilds.azureedge.net/public/Sdk/8.0.100-rtm.23512.16/dotnet-sdk-8.0.100-rtm.23512.16-linux-x64.tar.gz) + 3. [MacOS x64 link](https://dotnetbuilds.azureedge.net/public/Sdk/8.0.100-rtm.23512.16/dotnet-sdk-8.0.100-rtm.23512.16-osx-x64.tar.gz) + 4. [MacOS ARM64](https://dotnetbuilds.azureedge.net/public/Sdk/8.0.100-rtm.23512.16/dotnet-sdk-8.0.100-rtm.23512.16-osx-arm64.tar.gz) + +## Install Docker Desktop +* [Windows](https://docs.docker.com/desktop/install/windows-install/) +* [MacOS X](https://docs.docker.com/desktop/install/mac-install/) +* [Linux](https://docs.docker.com/desktop/install/linux-install/) + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/docs/open-telemetry-architecture.md dotnet8-8.0.100-8.0.0/src/aspire/docs/open-telemetry-architecture.md --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/docs/open-telemetry-architecture.md 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/docs/open-telemetry-architecture.md 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,60 @@ +# Aspire OpenTelemetry architecture + +One of Aspire's objectives is to ensure that apps are straightforward to debug and diagnose. By default, Aspire apps are configured to collect and export telemetry using [OpenTelemetry (OTEL)](https://opentelemetry.io/). Additionally, Aspire local development includes UI in the dashboard for viewing OTEL data. Telemetry just works and is easy to use. + +This document details how OpenTelemtry is used in Aspire apps. + +## Telemetry types + +OTEL is focused on three kinds of telemetry: structured logging, tracing, and metrics. .NET libraries and apps have APIs for recording each kind of telemetry: + +* Structured logging: Log entries from `ILogger`. +* Tracing: Distributed tracing from `Activity`. +* Metrics: Numeric values from `Meter` and `Instrument`. + +When an OpenTelemetry SDK is configured in an app, it receives data from these APIs. + +## OpenTelemetry SDK + +The [.NET OpenTelemetry SDK](https://github.com/open-telemetry/opentelemetry-dotnet) offers features for gathering data from several .NET APIs, including `ILogger`, `Activity`, `Meter`, and `Instrument`. It then facilitates the export of this telemetry data to a data store or reporting tool. The telemetry export mechanism relies on the [OpenTelemetry protocol (OTLP)](https://opentelemetry.io/docs/specs/otel/protocol/), which serves as a standardized approach for transmitting telemetry data through REST or gRPC. + +.NET projects setup the .NET OpenTelemetry SDK using the _service defaults_ project. Aspire templates automatically create the service defaults, and Aspire apps call it at startup. The service defaults enable collecting and exporting telemetry for .NET apps. + +## OpenTelemetry environment variables + +OTEL has a [list of known environment variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/) that configure the most important behavior for collecting and exporting telemetry. OTEL SDKs, including the .NET SDK, support reading these variables. + +Aspire apps launch with environment variables that configure the name and ID of the app in exported telemetry and set the address endpoint of the OTLP server to export data. For example: + +* `OTEL_SERVICE_NAME` = myfrontend +* `OTEL_RESOURCE_ATTRIBUTES` = service.instance.id=1a5f9c1e-e5ba-451b-95ee-ced1ee89c168 +* `OTEL_EXPORTER_OTLP_ENDPOINT` = http://localhost:4318 + +The environment variables are automatically set in local development. + +## Aspire local development + +The Aspire dashboard provides UI for viewing the telemetry of apps. Telemetry data is sent to the dashboard using OTLP, and the dashboard implements an OTLP server to receive telemetry data and store it in memory. The dashboard UI presents telemetry stored in memory. + +Aspire debugging workflow: + +* Developer starts the Aspire app with debugging, presses F5. +* Aspire dashboard and developer control plane (DCP) start. +* App configuration is run in the _AppHost_ project. + * OTEL environment variables are automatically added to .NET projects during app configuration. + * DCP provides the name (`OTEL_SERVICE_NAME`) and ID (`OTEL_RESOURCE_ATTRIBUTES`) of the app in exported telemetry. + * The OTLP endpoint is an HTTP/2 port started by the dashboard. This endpoint is set in the `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable on each project. That tells projects to export telemetry back to the dashboard. + * Small export intervals (`OTEL_BSP_SCHEDULE_DELAY`, `OTEL_BLRP_SCHEDULE_DELAY`, `OTEL_METRIC_EXPORT_INTERVAL`) so data is quickly available in the dashboard. Small values are used in local development to prioritize dashboard responsiveness over efficiency. +* The DCP starts configured projects, containers, and executables. +* Once started, apps send telemetry to the dashboard. +* Dashboard displays near real-time telemetry of all Aspire apps. + +## Aspire deployment + +Aspire deployment environments should configure OTEL environment variables that make sense for their environment. For example, `OTEL_EXPORTER_OTLP_ENDPOINT` should be configured to the environment's local OTLP collector or monitoring service. + +Aspire telemetry works best in environments that support OTLP. OTLP exporting is disabled if `OTEL_EXPORTER_OTLP_ENDPOINT` isn't configured. + +## Non-.NET apps + +OTEL isn't limited to .NET projects. Apps and containers that include OTEL can be passed environment variables to configure exporting telemetry. For example, the dapr sidecar (written in golang) includes OTEL and standard OTEL environment variables can be used to enable telemetry. diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/docs/repo-instructions.md dotnet8-8.0.100-8.0.0/src/aspire/docs/repo-instructions.md --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/docs/repo-instructions.md 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/docs/repo-instructions.md 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,52 @@ +# Set up your environment + +Follow all steps in [machine-requirements](machine-requirements.md). + +# Building the repo + +You can build the repo from the command line by simply `.\build.cmd`. + +You can launch VS with: + +1. `.\restore.cmd` +2. `.\startvs.cmd` + +# Run the Aspire eShopLite sample + +## Enable Azure ServiceBus (optional) + +1. Follow [these instructions](https://learn.microsoft.com/azure/service-bus-messaging/service-bus-dotnet-get-started-with-queues?tabs=passwordless#create-a-namespace-in-the-azure-portal) to create a ServiceBus Namespace (pick a unique namespace) and a Queue (explicitly name the Queue "orders"). + - Be sure to follow the "Assign roles to your Azure AD user" and "Launch Visual Studio and sign-in to Azure" sections so the app can authenticate as you. +2. Add the following user-secret to the `samples\eShopLite\AppHost` orchestrator project (using the unique namespace you created above): + +```shell +C:\git\aspire\samples\eShopLite\AppHost> dotnet user-secrets set ConnectionStrings:messaging .servicebus.windows.net +``` + +- You can do the same in VS by right-clicking AppHost in the Solution Explorer -> "Manage User Secrets" and add + +```json +{ + "ConnectionStrings": { + "messaging": ".servicebus.windows.net" + } +} +``` + +- The `` is labeled in the portal UI as "Host name" e.g. myservicebusinstance.servicebus.windows.net + +## Load the Sample Application + +1. Make sure Docker Desktop is running +2. Open `Aspire.sln` solution +3. Set `AppHost` project under `\samples\eShopLite` folder to be the startup project. Make sure the launch profile is set to "http". +4. F5 and enjoy. +5. When you are done, "Stop Debugging". + +# Dashboard + +Starting debugging in VS will automatically launch the browser with dashboard which is being served at URL "http://localhost:15888" by default. The URL is controlled by launchSettings.json file in AppHost project. + +# Tips and known issues + +See the [tips and known issues](tips-and-known-issues.md) page. diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/docs/specs/manifest-spec.md dotnet8-8.0.100-8.0.0/src/aspire/docs/specs/manifest-spec.md --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/docs/specs/manifest-spec.md 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/docs/specs/manifest-spec.md 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,112 @@ +# Manifest Specification for Aspire's Distributed Application Model + +This is a specification for the manifest file for Aspire's Distributed Application Model. The purpose of the manifest file is to allow developers to export definitions of components that comprise their distributed application model and their dependencies so that other tools can process it to facilitate deployment into target runtime environments. + +The format of the manifest file itself does not pre-suppose a particular target environment but this document will make reference to specific cloud providers and technologies for illustrative purposes. + +## Basic model + +The Aspire distributed application model is comprised components which are typically deployed together as a unit. For example there may be a front-end ASP.NET Core application which calls into one or more backend services which in turn may depend on relational databases or caches. Consider the following sample (taken from the eShop light example): + +```csharp +using Aspire.Hosting.Postgres; +using Aspire.Hosting.Redis; +using Projects = eShopLite.App.Projects; + +var builder = DistributedApplication.CreateBuilder(args); + +var catalogDb = builder.AddPostgresContainer("postgres").AddDatabase("catalogdb"); +var redis = builder.AddRedisContainer("redis"); + +var catalog = builder.AddProject("catalogservice") + .WithReference(catalogDb); + +var basket = builder.AddProject("basketservice") + .WithReference(redis); + +builder.AddProject("frontend") + .WithServiceReference(basket) + .WithServiceReference(catalog) + .IsExternal(); + +builder.AddContainer("prometheus", "prom/prometheus") + .WithServiceBinding(9090); + +builder.Build().Run(); +``` + +When ```dotnet publish``` is called on the AppHost project containing the code above the application model and dependency projects will be built and the AppHost will be executed in a model which emits an ```aspire-manifest.json``` file in the build artifacts for the AppHost project. The manifest file for the above project would look like the following: + +```json +{ + "$schema": "&2 + exit /b %ERRORLEVEL% +) + +set /p dotnetPath=<%~dp0artifacts\toolset\sdk.txt + +:: Disable first run since we want to control all package sources +set DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + +call "%dotnetPath%\dotnet.exe" %* \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/dotnet.sh dotnet8-8.0.100-8.0.0/src/aspire/dotnet.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/dotnet.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/dotnet.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +# Disable first run since we want to control all package sources +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + +source $scriptroot/eng/common/tools.sh + +InitializeDotNetCli true # Install +__dotnetDir=${_InitializeDotNetCli} + +dotnetPath=${__dotnetDir}/dotnet +${dotnetPath} "$@" \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/.editorconfig dotnet8-8.0.100-8.0.0/src/aspire/.editorconfig --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/.editorconfig 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/.editorconfig 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,477 @@ +; EditorConfig to support per-solution formatting. +; Use the EditorConfig VS add-in to make this work. +; http://editorconfig.org/ +; +; Here are some resources for what's supported for .NET/C# +; https://kent-boogaart.com/blog/editorconfig-reference-for-c-developers +; https://learn.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference +; +; Be **careful** editing this because some of the rules don't support adding a severity level +; For instance if you change to `dotnet_sort_system_directives_first = true:warning` (adding `:warning`) +; then the rule will be silently ignored. + +; This is the default for the codeline. +root = true + +[*] +indent_style = space +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.cs] +indent_size = 4 +dotnet_sort_system_directives_first = true + +# Don't use this. qualifier +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion + +# use int x = .. over Int32 +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion + +# use int.MaxValue over Int32.MaxValue +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Require var all the time. +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion + +# Disallow throw expressions. +csharp_style_throw_expression = false:suggestion + +# Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true + +# Namespace settings +csharp_style_namespace_declarations = file_scoped + +# Brace settings +csharp_prefer_braces = true # Prefer curly braces even for one line of code + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = warning +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = warning +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = warning +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +[*.{xml,config,*proj,nuspec,props,resx,targets,yml,tasks}] +indent_size = 2 + +# Xml config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +[*.json] +indent_size = 2 + +[*.{ps1,psm1}] +indent_size = 4 + +[*.sh] +indent_size = 4 +end_of_line = lf + +[*.{razor,cshtml}] +charset = utf-8-bom + +[*.{cs,vb}] + +# SYSLIB1054: Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time +dotnet_diagnostic.SYSLIB1054.severity = warning + +# CA1018: Mark attributes with AttributeUsageAttribute +dotnet_diagnostic.CA1018.severity = warning + +# CA1047: Do not declare protected member in sealed type +dotnet_diagnostic.CA1047.severity = warning + +# CA1305: Specify IFormatProvider +dotnet_diagnostic.CA1305.severity = warning + +# CA1507: Use nameof to express symbol names +dotnet_diagnostic.CA1507.severity = warning + +# CA1510: Use ArgumentNullException throw helper +dotnet_diagnostic.CA1510.severity = warning + +# CA1511: Use ArgumentException throw helper +dotnet_diagnostic.CA1511.severity = warning + +# CA1512: Use ArgumentOutOfRangeException throw helper +dotnet_diagnostic.CA1512.severity = warning + +# CA1513: Use ObjectDisposedException throw helper +dotnet_diagnostic.CA1513.severity = warning + +# CA1725: Parameter names should match base declaration +dotnet_diagnostic.CA1725.severity = suggestion + +# CA1802: Use literals where appropriate +dotnet_diagnostic.CA1802.severity = warning + +# CA1805: Do not initialize unnecessarily +dotnet_diagnostic.CA1805.severity = warning + +# CA1810: Do not initialize unnecessarily +dotnet_diagnostic.CA1810.severity = warning + +# CA1821: Remove empty Finalizers +dotnet_diagnostic.CA1821.severity = warning + +# CA1822: Make member static +dotnet_diagnostic.CA1822.severity = warning +dotnet_code_quality.CA1822.api_surface = private, internal + +# CA1823: Avoid unused private fields +dotnet_diagnostic.CA1823.severity = warning + +# CA1825: Avoid zero-length array allocations +dotnet_diagnostic.CA1825.severity = warning + +# CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly +dotnet_diagnostic.CA1826.severity = warning + +# CA1827: Do not use Count() or LongCount() when Any() can be used +dotnet_diagnostic.CA1827.severity = warning + +# CA1828: Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used +dotnet_diagnostic.CA1828.severity = warning + +# CA1829: Use Length/Count property instead of Count() when available +dotnet_diagnostic.CA1829.severity = warning + +# CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder +dotnet_diagnostic.CA1830.severity = warning + +# CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1831.severity = warning + +# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1832.severity = warning + +# CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1833.severity = warning + +# CA1834: Consider using 'StringBuilder.Append(char)' when applicable +dotnet_diagnostic.CA1834.severity = warning + +# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' +dotnet_diagnostic.CA1835.severity = warning + +# CA1836: Prefer IsEmpty over Count +dotnet_diagnostic.CA1836.severity = warning + +# CA1837: Use 'Environment.ProcessId' +dotnet_diagnostic.CA1837.severity = warning + +# CA1838: Avoid 'StringBuilder' parameters for P/Invokes +dotnet_diagnostic.CA1838.severity = warning + +# CA1839: Use 'Environment.ProcessPath' +dotnet_diagnostic.CA1839.severity = warning + +# CA1840: Use 'Environment.CurrentManagedThreadId' +dotnet_diagnostic.CA1840.severity = warning + +# CA1841: Prefer Dictionary.Contains methods +dotnet_diagnostic.CA1841.severity = warning + +# CA1842: Do not use 'WhenAll' with a single task +dotnet_diagnostic.CA1842.severity = warning + +# CA1843: Do not use 'WaitAll' with a single task +dotnet_diagnostic.CA1843.severity = warning + +# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream' +dotnet_diagnostic.CA1844.severity = warning + +# CA1845: Use span-based 'string.Concat' +dotnet_diagnostic.CA1845.severity = warning + +# CA1846: Prefer AsSpan over Substring +dotnet_diagnostic.CA1846.severity = warning + +# CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters +dotnet_diagnostic.CA1847.severity = warning + +# CA1852: Seal internal types +dotnet_diagnostic.CA1852.severity = warning + +# CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method +dotnet_diagnostic.CA1854.severity = warning + +# CA1855: Prefer 'Clear' over 'Fill' +dotnet_diagnostic.CA1855.severity = warning + +# CA1856: Incorrect usage of ConstantExpected attribute +dotnet_diagnostic.CA1856.severity = error + +# CA1857: A constant is expected for the parameter +dotnet_diagnostic.CA1857.severity = warning + +# CA1858: Use 'StartsWith' instead of 'IndexOf' +dotnet_diagnostic.CA1858.severity = warning + +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = warning + +# CA2008: Do not create tasks without passing a TaskScheduler +dotnet_diagnostic.CA2008.severity = warning + +# CA2009: Do not call ToImmutableCollection on an ImmutableCollection value +dotnet_diagnostic.CA2009.severity = warning + +# CA2011: Avoid infinite recursion +dotnet_diagnostic.CA2011.severity = warning + +# CA2012: Use ValueTask correctly +dotnet_diagnostic.CA2012.severity = warning + +# CA2013: Do not use ReferenceEquals with value types +dotnet_diagnostic.CA2013.severity = warning + +# CA2014: Do not use stackalloc in loops. +dotnet_diagnostic.CA2014.severity = warning + +# CA2016: Forward the 'CancellationToken' parameter to methods that take one +dotnet_diagnostic.CA2016.severity = warning + +# CA2200: Rethrow to preserve stack details +dotnet_diagnostic.CA2200.severity = warning + +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.CA2201.severity = warning + +# CA2208: Instantiate argument exceptions correctly +dotnet_diagnostic.CA2208.severity = warning + +# CA2245: Do not assign a property to itself +dotnet_diagnostic.CA2245.severity = warning + +# CA2246: Assigning symbol and its member in the same statement +dotnet_diagnostic.CA2246.severity = warning + +# CA2249: Use string.Contains instead of string.IndexOf to improve readability. +dotnet_diagnostic.CA2249.severity = warning + +# IDE0005: Remove unnecessary usings +dotnet_diagnostic.IDE0005.severity = warning + +# IDE0011: Curly braces to surround blocks of code +dotnet_diagnostic.IDE0011.severity = warning + +# IDE0020: Use pattern matching to avoid is check followed by a cast (with variable) +dotnet_diagnostic.IDE0020.severity = warning + +# IDE0029: Use coalesce expression (non-nullable types) +dotnet_diagnostic.IDE0029.severity = warning + +# IDE0030: Use coalesce expression (nullable types) +dotnet_diagnostic.IDE0030.severity = warning + +# IDE0031: Use null propagation +dotnet_diagnostic.IDE0031.severity = warning + +# IDE0035: Remove unreachable code +dotnet_diagnostic.IDE0035.severity = warning + +# IDE0036: Order modifiers +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion +dotnet_diagnostic.IDE0036.severity = warning + +# IDE0038: Use pattern matching to avoid is check followed by a cast (without variable) +dotnet_diagnostic.IDE0038.severity = warning + +# IDE0043: Format string contains invalid placeholder +dotnet_diagnostic.IDE0043.severity = warning + +# IDE0044: Make field readonly +dotnet_diagnostic.IDE0044.severity = warning + +# IDE0051: Remove unused private members +dotnet_diagnostic.IDE0051.severity = warning + +# IDE0055: All formatting rules +dotnet_diagnostic.IDE0055.severity = suggestion + +# IDE0059: Unnecessary assignment to a value +dotnet_diagnostic.IDE0059.severity = warning + +# IDE0060: Remove unused parameter +dotnet_code_quality_unused_parameters = non_public +dotnet_diagnostic.IDE0060.severity = warning + +# IDE0062: Make local function static +dotnet_diagnostic.IDE0062.severity = warning + +# IDE0073: File header +dotnet_diagnostic.IDE0073.severity = warning +file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license. + +# IDE1006: Required naming style +dotnet_diagnostic.IDE1006.severity = warning + +# IDE0161: Convert to file-scoped namespace +dotnet_diagnostic.IDE0161.severity = warning + +# IDE0200: Lambda expression can be removed +dotnet_diagnostic.IDE0200.severity = warning + +# IDE2000: Disallow multiple blank lines +dotnet_style_allow_multiple_blank_lines_experimental = false +dotnet_diagnostic.IDE2000.severity = warning + +[{eng/tools/**.cs,**/{test,testassets,samples,Samples,perf,benchmarkapps,scripts,stress}/**.cs,src/Hosting/Server.IntegrationTesting/**.cs,src/Servers/IIS/IntegrationTesting.IIS/**.cs,src/Shared/Http2cat/**.cs,src/Testing/**.cs}] +# CA1018: Mark attributes with AttributeUsageAttribute +dotnet_diagnostic.CA1018.severity = suggestion +# CA1507: Use nameof to express symbol names +dotnet_diagnostic.CA1507.severity = suggestion +# CA1510: Use ArgumentNullException throw helper +dotnet_diagnostic.CA1510.severity = suggestion +# CA1511: Use ArgumentException throw helper +dotnet_diagnostic.CA1511.severity = suggestion +# CA1512: Use ArgumentOutOfRangeException throw helper +dotnet_diagnostic.CA1512.severity = suggestion +# CA1513: Use ObjectDisposedException throw helper +dotnet_diagnostic.CA1513.severity = suggestion +# CA1802: Use literals where appropriate +dotnet_diagnostic.CA1802.severity = suggestion +# CA1805: Do not initialize unnecessarily +dotnet_diagnostic.CA1805.severity = suggestion +# CA1810: Do not initialize unnecessarily +dotnet_diagnostic.CA1810.severity = suggestion +# CA1822: Make member static +dotnet_diagnostic.CA1822.severity = suggestion +# CA1823: Avoid zero-length array allocations +dotnet_diagnostic.CA1825.severity = suggestion +# CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly +dotnet_diagnostic.CA1826.severity = suggestion +# CA1827: Do not use Count() or LongCount() when Any() can be used +dotnet_diagnostic.CA1827.severity = suggestion +# CA1829: Use Length/Count property instead of Count() when available +dotnet_diagnostic.CA1829.severity = suggestion +# CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1831.severity = suggestion +# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1832.severity = suggestion +# CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1833.severity = suggestion +# CA1834: Consider using 'StringBuilder.Append(char)' when applicable +dotnet_diagnostic.CA1834.severity = suggestion +# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' +dotnet_diagnostic.CA1835.severity = suggestion +# CA1837: Use 'Environment.ProcessId' +dotnet_diagnostic.CA1837.severity = suggestion +# CA1838: Avoid 'StringBuilder' parameters for P/Invokes +dotnet_diagnostic.CA1838.severity = suggestion +# CA1841: Prefer Dictionary.Contains methods +dotnet_diagnostic.CA1841.severity = suggestion +# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream' +dotnet_diagnostic.CA1844.severity = suggestion +# CA1845: Use span-based 'string.Concat' +dotnet_diagnostic.CA1845.severity = suggestion +# CA1846: Prefer AsSpan over Substring +dotnet_diagnostic.CA1846.severity = suggestion +# CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters +dotnet_diagnostic.CA1847.severity = suggestion +# CA1852: Seal internal types +dotnet_diagnostic.CA1852.severity = suggestion +# CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method +dotnet_diagnostic.CA1854.severity = suggestion +# CA1855: Prefer 'Clear' over 'Fill' +dotnet_diagnostic.CA1855.severity = suggestion +# CA1856: Incorrect usage of ConstantExpected attribute +dotnet_diagnostic.CA1856.severity = suggestion +# CA1857: A constant is expected for the parameter +dotnet_diagnostic.CA1857.severity = suggestion +# CA1858: Use 'StartsWith' instead of 'IndexOf' +dotnet_diagnostic.CA1858.severity = suggestion +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = suggestion +# CA2008: Do not create tasks without passing a TaskScheduler +dotnet_diagnostic.CA2008.severity = suggestion +# CA2012: Use ValueTask correctly +dotnet_diagnostic.CA2012.severity = suggestion +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.CA2201.severity = suggestion +# CA2249: Use string.Contains instead of string.IndexOf to improve readability. +dotnet_diagnostic.CA2249.severity = suggestion +# IDE0005: Remove unnecessary usings +dotnet_diagnostic.IDE0005.severity = suggestion +# IDE0020: Use pattern matching to avoid is check followed by a cast (with variable) +dotnet_diagnostic.IDE0020.severity = suggestion +# IDE0029: Use coalesce expression (non-nullable types) +dotnet_diagnostic.IDE0029.severity = suggestion +# IDE0030: Use coalesce expression (nullable types) +dotnet_diagnostic.IDE0030.severity = suggestion +# IDE0031: Use null propagation +dotnet_diagnostic.IDE0031.severity = suggestion +# IDE0038: Use pattern matching to avoid is check followed by a cast (without variable) +dotnet_diagnostic.IDE0038.severity = suggestion +# IDE0044: Make field readonly +dotnet_diagnostic.IDE0044.severity = suggestion +# IDE0051: Remove unused private members +dotnet_diagnostic.IDE0051.severity = suggestion +# IDE0059: Unnecessary assignment to a value +dotnet_diagnostic.IDE0059.severity = suggestion +# IDE0060: Remove unused parameters +dotnet_diagnostic.IDE0060.severity = suggestion +# IDE0062: Make local function static +dotnet_diagnostic.IDE0062.severity = suggestion +# IDE0200: Lambda expression can be removed +dotnet_diagnostic.IDE0200.severity = suggestion + +# CA2016: Forward the 'CancellationToken' parameter to methods that take one +dotnet_diagnostic.CA2016.severity = suggestion + +# Defaults for content in the shared src/ and shared runtime dir + +[{**/Shared/runtime/**.{cs,vb},src/Shared/test/Shared.Tests/runtime/**.{cs,vb},**/microsoft.extensions.hostfactoryresolver.sources/**.{cs,vb}}] +# CA1822: Make member static +dotnet_diagnostic.CA1822.severity = silent +# IDE0011: Use braces +dotnet_diagnostic.IDE0011.severity = silent +# IDE0055: Fix formatting +dotnet_diagnostic.IDE0055.severity = silent +# IDE0060: Remove unused parameters +dotnet_diagnostic.IDE0060.severity = silent +# IDE0062: Make local function static +dotnet_diagnostic.IDE0062.severity = silent +# IDE0161: Convert to file-scoped namespace +dotnet_diagnostic.IDE0161.severity = silent + +[{**/Shared/**.cs,**/microsoft.extensions.hostfactoryresolver.sources/**.{cs,vb}}] +# IDE0005: Remove unused usings. Ignore for shared src files since imports for those depend on the projects in which they are included. +dotnet_diagnostic.IDE0005.severity = silent + +[{*.razor.cs,src/Aspire.Dashboard/Components/**.cs}] +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = silent \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/Build.props dotnet8-8.0.100-8.0.0/src/aspire/eng/Build.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/Build.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/Build.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/build.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/build.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/build.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/build.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,108 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [switch][Alias('h')]$help, + [switch][Alias('t')]$test, + [ValidateSet("Debug","Release")][string[]][Alias('c')]$configuration = @("Debug"), + [string][Alias('v')]$verbosity = "minimal", + [switch]$vs, + [ValidateSet("windows","linux","osx")][string]$os, + [switch]$testnobuild, + [ValidateSet("x86","x64","arm","arm64")][string[]][Alias('a')]$arch = @([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLowerInvariant()), + [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties +) + +function Get-Help() { + Write-Host "Common settings:" + Write-Host " -arch (-a) Target platform: x86, x64, arm or arm64." + Write-Host " [Default: Your machine's architecture.]" + Write-Host " -binaryLog (-bl) Output binary log." + Write-Host " -configuration (-c) Build configuration: Debug or Release." + Write-Host " [Default: Debug]" + Write-Host " -help (-h) Print help and exit." + Write-Host " -os Target operating system: windows, linux or osx." + Write-Host " [Default: Your machine's OS.]" + Write-Host " -verbosity (-v) MSBuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]." + Write-Host " [Default: Minimal]" + Write-Host " -vs Open the solution with Visual Studio using the locally acquired SDK." + Write-Host "" + + Write-Host "Actions (defaults to -restore -build):" + Write-Host " -build (-b) Build all source projects." + Write-Host " This assumes -restore has been run already." + Write-Host " -clean Clean the solution." + Write-Host " -pack Package build outputs into NuGet packages." + Write-Host " -publish Publish artifacts (e.g. symbols)." + Write-Host " This assumes -build has been run already." + Write-Host " -rebuild Rebuild all source projects." + Write-Host " -restore Restore dependencies." + Write-Host " -sign Sign build outputs." + Write-Host " -test (-t) Incrementally builds and runs tests." + Write-Host " Use in conjunction with -testnobuild to only run tests." + Write-Host "" + + Write-Host "Libraries settings:" + Write-Host " -testnobuild Skip building tests when invoking -test." + Write-Host "" + + Write-Host "Command-line arguments not listed above are passed through to MSBuild." + Write-Host "The above arguments can be shortened as much as to be unambiguous." + Write-Host "(Example: -con for configuration, -t for test, etc.)." + Write-Host "" +} + +if ($help) { + Get-Help + exit 0 +} + +if ($vs) { + $solution = Split-Path $PSScriptRoot -Parent | Join-Path -ChildPath "Aspire.sln" + + . $PSScriptRoot\common\tools.ps1 + + # This tells .NET Core to use the bootstrapped runtime + $env:DOTNET_ROOT=InitializeDotNetCli -install:$true -createSdkLocationFile:$true + + # This tells MSBuild to load the SDK from the directory of the bootstrapped SDK + $env:DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR=$env:DOTNET_ROOT + + # Put our local dotnet.exe on PATH first so Visual Studio knows which one to use + $env:PATH=($env:DOTNET_ROOT + ";" + $env:PATH); + + # Launch Visual Studio with the locally defined environment variables + ."$solution" + + exit 0 +} + +# Check if an action is passed in +$actions = "b","build","r","restore","rebuild","sign","testnobuild","publish","clean" +$actionPassedIn = @(Compare-Object -ReferenceObject @($PSBoundParameters.Keys) -DifferenceObject $actions -ExcludeDifferent -IncludeEqual).Length -ne 0 +if ($null -ne $properties -and $actionPassedIn -ne $true) { + $actionPassedIn = @(Compare-Object -ReferenceObject $properties -DifferenceObject $actions.ForEach({ "-" + $_ }) -ExcludeDifferent -IncludeEqual).Length -ne 0 +} + +if (!$actionPassedIn) { + $arguments = "-restore -build" +} + +foreach ($argument in $PSBoundParameters.Keys) +{ + switch($argument) + { + "os" { $arguments += " /p:TargetOS=$($PSBoundParameters[$argument])" } + "properties" { $arguments += " " + $properties } + "verbosity" { $arguments += " -$argument " + $($PSBoundParameters[$argument]) } + "configuration" { $configuration = (Get-Culture).TextInfo.ToTitleCase($($PSBoundParameters[$argument])); $arguments += " -configuration $configuration" } + "arch" { $arguments += " /p:TargetArchitecture=$($PSBoundParameters[$argument])" } + default { $arguments += " /p:$argument=$($PSBoundParameters[$argument])" } + } +} + +if ($env:TreatWarningsAsErrors -eq 'false') { + $arguments += " -warnAsError 0" +} + +Invoke-Expression "& `"$PSScriptRoot/common/build.ps1`" $arguments" + +exit 0 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/build.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/build.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/build.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/build.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,155 @@ +#!/usr/bin/env bash + +set -ue + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +usage() +{ + echo "Common settings:" + echo " --arch (-a) Target platform: x86, x64, arm or arm64." + echo " [Default: Your machine's architecture.]" + echo " --binaryLog (-bl) Output binary log." + echo " --configuration (-c) Build configuration: Debug or Release." + echo " [Default: Debug]" + echo " --help (-h) Print help and exit." + echo " --os Target operating system: windows, linux, or osx." + echo " [Default: Your machine's OS.]" + echo " --verbosity (-v) MSBuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]." + echo " [Default: Minimal]" + echo "" + + echo "Actions (defaults to --restore --build):" + echo " --build (-b) Build all source projects." + echo " This assumes --restore has been run already." + echo " --clean Clean the solution." + echo " --pack Package build outputs into NuGet packages." + echo " --publish Publish artifacts (e.g. symbols)." + echo " This assumes --build has been run already." + echo " --rebuild Rebuild all source projects." + echo " --restore (-r) Restore dependencies." + echo " --sign Sign build outputs." + echo " --test (-t) Incrementally builds and runs tests." + echo " Use in conjunction with --testnobuild to only run tests." + echo "" + + echo "Libraries settings:" + echo " --testnobuild Skip building tests when invoking -test." + echo "" + + echo "Command line arguments starting with '/p:' are passed through to MSBuild." + echo "Arguments can also be passed in with a single hyphen." + echo "" +} + +arguments='' +extraargs='' + +# Check if an action is passed in +declare -a actions=("b" "build" "r" "restore" "rebuild" "testnobuild" "sign" "publish" "clean") +actInt=($(comm -12 <(printf '%s\n' "${actions[@]/#/-}" | sort) <(printf '%s\n' "${@/#--/-}" | sort))) + +while [[ $# > 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + + case "$opt" in + -help|-h|-\?|/?) + usage + exit 0 + ;; + + -arch|-a) + if [ -z ${2+x} ]; then + echo "No architecture supplied. See help (--help) for supported architectures." 1>&2 + exit 1 + fi + passedArch="$(echo "$2" | tr "[:upper:]" "[:lower:]")" + case "$passedArch" in + x64|x86|arm|arm64) + arch=$passedArch + ;; + *) + echo "Unsupported target architecture '$2'." + echo "The allowed values are x86, x64, arm, arm64." + exit 1 + ;; + esac + arguments="$arguments /p:TargetArchitecture=$arch" + shift 2 + ;; + + -configuration|-c) + if [ -z ${2+x} ]; then + echo "No configuration supplied. See help (--help) for supported configurations." 1>&2 + exit 1 + fi + passedConfig="$(echo "$2" | tr "[:upper:]" "[:lower:]")" + case "$passedConfig" in + debug|release) + val="$(tr '[:lower:]' '[:upper:]' <<< ${passedConfig:0:1})${passedConfig:1}" + ;; + *) + echo "Unsupported target configuration '$2'." + echo "The allowed values are Debug and Release." + exit 1 + ;; + esac + arguments="$arguments -configuration $val" + shift 2 + ;; + + -os) + if [ -z ${2+x} ]; then + echo "No target operating system supplied. See help (--help) for supported target operating systems." 1>&2 + exit 1 + fi + passedOS="$(echo "$2" | tr "[:upper:]" "[:lower:]")" + case "$passedOS" in + windows) + os="windows" ;; + linux) + os="linux" ;; + osx) + os="osx" ;; + *) + echo "Unsupported target OS '$2'." + echo "Try 'build.sh --help' for values supported by '--os'." + exit 1 + ;; + esac + arguments="$arguments /p:TargetOS=$os" + shift 2 + ;; + + -testnobuild) + arguments="$arguments /p:TestNoBuild=true" + shift 1 + ;; + + *) + extraargs="$extraargs $1" + shift 1 + ;; + esac +done + +if [ ${#actInt[@]} -eq 0 ]; then + arguments="-restore -build $arguments" +fi + +if [[ "${TreatWarningsAsErrors:-}" == "false" ]]; then + arguments="$arguments -warnAsError 0" +fi + +arguments="$arguments $extraargs" +"$scriptroot/common/build.sh" $arguments \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/BuildConfiguration/build-configuration.json dotnet8-8.0.100-8.0.0/src/aspire/eng/common/BuildConfiguration/build-configuration.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/BuildConfiguration/build-configuration.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/BuildConfiguration/build-configuration.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,4 @@ +{ + "RetryCountLimit": 1, + "RetryByAnyError": false +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/build.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/build.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/build.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/build.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,166 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string][Alias('c')]$configuration = "Debug", + [string]$platform = $null, + [string] $projects, + [string][Alias('v')]$verbosity = "minimal", + [string] $msbuildEngine = $null, + [bool] $warnAsError = $true, + [bool] $nodeReuse = $true, + [switch][Alias('r')]$restore, + [switch] $deployDeps, + [switch][Alias('b')]$build, + [switch] $rebuild, + [switch] $deploy, + [switch][Alias('t')]$test, + [switch] $integrationTest, + [switch] $performanceTest, + [switch] $sign, + [switch] $pack, + [switch] $publish, + [switch] $clean, + [switch][Alias('bl')]$binaryLog, + [switch][Alias('nobl')]$excludeCIBinarylog, + [switch] $ci, + [switch] $prepareMachine, + [string] $runtimeSourceFeed = '', + [string] $runtimeSourceFeedKey = '', + [switch] $excludePrereleaseVS, + [switch] $nativeToolsOnMachine, + [switch] $help, + [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties +) + +# Unset 'Platform' environment variable to avoid unwanted collision in InstallDotNetCore.targets file +# some computer has this env var defined (e.g. Some HP) +if($env:Platform) { + $env:Platform="" +} +function Print-Usage() { + Write-Host "Common settings:" + Write-Host " -configuration Build configuration: 'Debug' or 'Release' (short: -c)" + Write-Host " -platform Platform configuration: 'x86', 'x64' or any valid Platform value to pass to msbuild" + Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" + Write-Host " -binaryLog Output binary log (short: -bl)" + Write-Host " -help Print help and exit" + Write-Host "" + + Write-Host "Actions:" + Write-Host " -restore Restore dependencies (short: -r)" + Write-Host " -build Build solution (short: -b)" + Write-Host " -rebuild Rebuild solution" + Write-Host " -deploy Deploy built VSIXes" + Write-Host " -deployDeps Deploy dependencies (e.g. VSIXes for integration tests)" + Write-Host " -test Run all unit tests in the solution (short: -t)" + Write-Host " -integrationTest Run all integration tests in the solution" + Write-Host " -performanceTest Run all performance tests in the solution" + Write-Host " -pack Package build outputs into NuGet packages and Willow components" + Write-Host " -sign Sign build outputs" + Write-Host " -publish Publish artifacts (e.g. symbols)" + Write-Host " -clean Clean the solution" + Write-Host "" + + Write-Host "Advanced settings:" + Write-Host " -projects Semi-colon delimited list of sln/proj's to build. Globbing is supported (*.sln)" + Write-Host " -ci Set when running on CI server" + Write-Host " -excludeCIBinarylog Don't output binary log (short: -nobl)" + Write-Host " -prepareMachine Prepare machine for CI run, clean up processes after build" + Write-Host " -warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" + Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." + Write-Host " -excludePrereleaseVS Set to exclude build engines in prerelease versions of Visual Studio" + Write-Host " -nativeToolsOnMachine Sets the native tools on machine environment variable (indicating that the script should use native tools on machine)" + Write-Host "" + + Write-Host "Command line arguments not listed above are passed thru to msbuild." + Write-Host "The above arguments can be shortened as much as to be unambiguous (e.g. -co for configuration, -t for test, etc.)." +} + +. $PSScriptRoot\tools.ps1 + +function InitializeCustomToolset { + if (-not $restore) { + return + } + + $script = Join-Path $EngRoot 'restore-toolset.ps1' + + if (Test-Path $script) { + . $script + } +} + +function Build { + $toolsetBuildProj = InitializeToolset + InitializeCustomToolset + + $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'Build.binlog') } else { '' } + $platformArg = if ($platform) { "/p:Platform=$platform" } else { '' } + + if ($projects) { + # Re-assign properties to a new variable because PowerShell doesn't let us append properties directly for unclear reasons. + # Explicitly set the type as string[] because otherwise PowerShell would make this char[] if $properties is empty. + [string[]] $msbuildArgs = $properties + + # Resolve relative project paths into full paths + $projects = ($projects.Split(';').ForEach({Resolve-Path $_}) -join ';') + + $msbuildArgs += "/p:Projects=$projects" + $properties = $msbuildArgs + } + + MSBuild $toolsetBuildProj ` + $bl ` + $platformArg ` + /p:Configuration=$configuration ` + /p:RepoRoot=$RepoRoot ` + /p:Restore=$restore ` + /p:DeployDeps=$deployDeps ` + /p:Build=$build ` + /p:Rebuild=$rebuild ` + /p:Deploy=$deploy ` + /p:Test=$test ` + /p:Pack=$pack ` + /p:IntegrationTest=$integrationTest ` + /p:PerformanceTest=$performanceTest ` + /p:Sign=$sign ` + /p:Publish=$publish ` + @properties +} + +try { + if ($clean) { + if (Test-Path $ArtifactsDir) { + Remove-Item -Recurse -Force $ArtifactsDir + Write-Host 'Artifacts directory deleted.' + } + exit 0 + } + + if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { + Print-Usage + exit 0 + } + + if ($ci) { + if (-not $excludeCIBinarylog) { + $binaryLog = $true + } + $nodeReuse = $false + } + + if ($nativeToolsOnMachine) { + $env:NativeToolsOnMachine = $true + } + if ($restore) { + InitializeNativeTools + } + + Build +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ + ExitWithExitCode 1 +} + +ExitWithExitCode 0 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/build.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/build.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/build.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/build.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,247 @@ +#!/usr/bin/env bash + +# Stop script if unbound variable found (use ${var:-} if intentional) +set -u + +# Stop script if command returns non-zero exit code. +# Prevents hidden errors caused by missing error code propagation. +set -e + +usage() +{ + echo "Common settings:" + echo " --configuration Build configuration: 'Debug' or 'Release' (short: -c)" + echo " --verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" + echo " --binaryLog Create MSBuild binary log (short: -bl)" + echo " --help Print help and exit (short: -h)" + echo "" + + echo "Actions:" + echo " --restore Restore dependencies (short: -r)" + echo " --build Build solution (short: -b)" + echo " --sourceBuild Source-build the solution (short: -sb)" + echo " Will additionally trigger the following actions: --restore, --build, --pack" + echo " If --configuration is not set explicitly, will also set it to 'Release'" + echo " --rebuild Rebuild solution" + echo " --test Run all unit tests in the solution (short: -t)" + echo " --integrationTest Run all integration tests in the solution" + echo " --performanceTest Run all performance tests in the solution" + echo " --pack Package build outputs into NuGet packages and Willow components" + echo " --sign Sign build outputs" + echo " --publish Publish artifacts (e.g. symbols)" + echo " --clean Clean the solution" + echo "" + + echo "Advanced settings:" + echo " --projects Project or solution file(s) to build" + echo " --ci Set when running on CI server" + echo " --excludeCIBinarylog Don't output binary log (short: -nobl)" + echo " --prepareMachine Prepare machine for CI run, clean up processes after build" + echo " --nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" + echo " --warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" + echo "" + echo "Command line arguments not listed above are passed thru to msbuild." + echo "Arguments can also be passed in with a single hyphen." +} + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +restore=false +build=false +source_build=false +rebuild=false +test=false +integration_test=false +performance_test=false +pack=false +publish=false +sign=false +public=false +ci=false +clean=false + +warn_as_error=true +node_reuse=true +binary_log=false +exclude_ci_binary_log=false +pipelines_log=false + +projects='' +configuration='' +prepare_machine=false +verbosity='minimal' +runtime_source_feed='' +runtime_source_feed_key='' + +properties='' +while [[ $# > 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -help|-h) + usage + exit 0 + ;; + -clean) + clean=true + ;; + -configuration|-c) + configuration=$2 + shift + ;; + -verbosity|-v) + verbosity=$2 + shift + ;; + -binarylog|-bl) + binary_log=true + ;; + -excludeCIBinarylog|-nobl) + exclude_ci_binary_log=true + ;; + -pipelineslog|-pl) + pipelines_log=true + ;; + -restore|-r) + restore=true + ;; + -build|-b) + build=true + ;; + -rebuild) + rebuild=true + ;; + -pack) + pack=true + ;; + -sourcebuild|-sb) + build=true + source_build=true + restore=true + pack=true + ;; + -test|-t) + test=true + ;; + -integrationtest) + integration_test=true + ;; + -performancetest) + performance_test=true + ;; + -sign) + sign=true + ;; + -publish) + publish=true + ;; + -preparemachine) + prepare_machine=true + ;; + -projects) + projects=$2 + shift + ;; + -ci) + ci=true + ;; + -warnaserror) + warn_as_error=$2 + shift + ;; + -nodereuse) + node_reuse=$2 + shift + ;; + -runtimesourcefeed) + runtime_source_feed=$2 + shift + ;; + -runtimesourcefeedkey) + runtime_source_feed_key=$2 + shift + ;; + *) + properties="$properties $1" + ;; + esac + + shift +done + +if [[ -z "$configuration" ]]; then + if [[ "$source_build" = true ]]; then configuration="Release"; else configuration="Debug"; fi +fi + +if [[ "$ci" == true ]]; then + pipelines_log=true + node_reuse=false + if [[ "$exclude_ci_binary_log" == false ]]; then + binary_log=true + fi +fi + +. "$scriptroot/tools.sh" + +function InitializeCustomToolset { + local script="$eng_root/restore-toolset.sh" + + if [[ -a "$script" ]]; then + . "$script" + fi +} + +function Build { + InitializeToolset + InitializeCustomToolset + + if [[ ! -z "$projects" ]]; then + properties="$properties /p:Projects=$projects" + fi + + local bl="" + if [[ "$binary_log" == true ]]; then + bl="/bl:\"$log_dir/Build.binlog\"" + fi + + MSBuild $_InitializeToolset \ + $bl \ + /p:Configuration=$configuration \ + /p:RepoRoot="$repo_root" \ + /p:Restore=$restore \ + /p:Build=$build \ + /p:ArcadeBuildFromSource=$source_build \ + /p:Rebuild=$rebuild \ + /p:Test=$test \ + /p:Pack=$pack \ + /p:IntegrationTest=$integration_test \ + /p:PerformanceTest=$performance_test \ + /p:Sign=$sign \ + /p:Publish=$publish \ + $properties + + ExitWithExitCode 0 +} + +if [[ "$clean" == true ]]; then + if [ -d "$artifacts_dir" ]; then + rm -rf $artifacts_dir + echo "Artifacts directory deleted." + fi + exit 0 +fi + +if [[ "$restore" == true ]]; then + InitializeNativeTools +fi + +Build diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/CIBuild.cmd dotnet8-8.0.100-8.0.0/src/aspire/eng/common/CIBuild.cmd --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/CIBuild.cmd 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/CIBuild.cmd 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,2 @@ +@echo off +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0Build.ps1""" -restore -build -test -sign -pack -publish -ci %*" \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cibuild.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cibuild.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cibuild.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cibuild.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where + # the symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/build.sh" --restore --build --test --pack --publish --ci $@ \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm/sources.list.bionic dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm/sources.list.bionic --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm/sources.list.bionic 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm/sources.list.bionic 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm/sources.list.focal dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm/sources.list.focal --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm/sources.list.focal 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm/sources.list.focal 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ focal main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ focal-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm/sources.list.jammy dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm/sources.list.jammy --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm/sources.list.jammy 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm/sources.list.jammy 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm/sources.list.jessie dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm/sources.list.jessie --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm/sources.list.jessie 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm/sources.list.jessie 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,3 @@ +# Debian (sid) # UNSTABLE +deb http://ftp.debian.org/debian/ sid main contrib non-free +deb-src http://ftp.debian.org/debian/ sid main contrib non-free diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm/sources.list.xenial dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm/sources.list.xenial --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm/sources.list.xenial 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm/sources.list.xenial 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm/sources.list.zesty dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm/sources.list.zesty --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm/sources.list.zesty 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm/sources.list.zesty 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm/tizen/tizen.patch dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm/tizen/tizen.patch --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm/tizen/tizen.patch 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm/tizen/tizen.patch 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so +--- a/usr/lib/libc.so 2016-12-30 23:00:08.284951863 +0900 ++++ b/usr/lib/libc.so 2016-12-30 23:00:32.140951815 +0900 +@@ -2,4 +2,4 @@ + Use the shared library, but some functions are only in + the static library, so try that secondarily. */ + OUTPUT_FORMAT(elf32-littlearm) +-GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /lib/ld-linux-armhf.so.3 ) ) ++GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux-armhf.so.3 ) ) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/sources.list.bionic dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/sources.list.bionic --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/sources.list.bionic 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/sources.list.bionic 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/sources.list.buster dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/sources.list.buster --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/sources.list.buster 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/sources.list.buster 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://deb.debian.org/debian buster main +deb-src http://deb.debian.org/debian buster main + +deb http://deb.debian.org/debian-security/ buster/updates main +deb-src http://deb.debian.org/debian-security/ buster/updates main + +deb http://deb.debian.org/debian buster-updates main +deb-src http://deb.debian.org/debian buster-updates main + +deb http://deb.debian.org/debian buster-backports main contrib non-free +deb-src http://deb.debian.org/debian buster-backports main contrib non-free diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/sources.list.focal dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/sources.list.focal --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/sources.list.focal 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/sources.list.focal 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ focal main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ focal-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/sources.list.jammy dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/sources.list.jammy --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/sources.list.jammy 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/sources.list.jammy 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/sources.list.stretch dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/sources.list.stretch --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/sources.list.stretch 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/sources.list.stretch 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,12 @@ +deb http://deb.debian.org/debian stretch main +deb-src http://deb.debian.org/debian stretch main + +deb http://deb.debian.org/debian-security/ stretch/updates main +deb-src http://deb.debian.org/debian-security/ stretch/updates main + +deb http://deb.debian.org/debian stretch-updates main +deb-src http://deb.debian.org/debian stretch-updates main + +deb http://deb.debian.org/debian stretch-backports main contrib non-free +deb-src http://deb.debian.org/debian stretch-backports main contrib non-free + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/sources.list.xenial dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/sources.list.xenial --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/sources.list.xenial 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/sources.list.xenial 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/sources.list.zesty dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/sources.list.zesty --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/sources.list.zesty 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/sources.list.zesty 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/tizen/tizen.patch dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/tizen/tizen.patch --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/arm64/tizen/tizen.patch 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/arm64/tizen/tizen.patch 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so +--- a/usr/lib64/libc.so 2016-12-30 23:00:08.284951863 +0900 ++++ b/usr/lib64/libc.so 2016-12-30 23:00:32.140951815 +0900 +@@ -2,4 +2,4 @@ + Use the shared library, but some functions are only in + the static library, so try that secondarily. */ + OUTPUT_FORMAT(elf64-littleaarch64) +-GROUP ( /lib64/libc.so.6 /usr/lib64/libc_nonshared.a AS_NEEDED ( /lib/ld-linux-aarch64.so.1 ) ) ++GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux-aarch64.so.1 ) ) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/armel/armel.jessie.patch dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/armel/armel.jessie.patch --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/armel/armel.jessie.patch 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/armel/armel.jessie.patch 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,43 @@ +diff -u -r a/usr/include/urcu/uatomic/generic.h b/usr/include/urcu/uatomic/generic.h +--- a/usr/include/urcu/uatomic/generic.h 2014-10-22 15:00:58.000000000 -0700 ++++ b/usr/include/urcu/uatomic/generic.h 2020-10-30 21:38:28.550000000 -0700 +@@ -69,10 +69,10 @@ + #endif + #ifdef UATOMIC_HAS_ATOMIC_SHORT + case 2: +- return __sync_val_compare_and_swap_2(addr, old, _new); ++ return __sync_val_compare_and_swap_2((uint16_t*) addr, old, _new); + #endif + case 4: +- return __sync_val_compare_and_swap_4(addr, old, _new); ++ return __sync_val_compare_and_swap_4((uint32_t*) addr, old, _new); + #if (CAA_BITS_PER_LONG == 64) + case 8: + return __sync_val_compare_and_swap_8(addr, old, _new); +@@ -109,7 +109,7 @@ + return; + #endif + case 4: +- __sync_and_and_fetch_4(addr, val); ++ __sync_and_and_fetch_4((uint32_t*) addr, val); + return; + #if (CAA_BITS_PER_LONG == 64) + case 8: +@@ -148,7 +148,7 @@ + return; + #endif + case 4: +- __sync_or_and_fetch_4(addr, val); ++ __sync_or_and_fetch_4((uint32_t*) addr, val); + return; + #if (CAA_BITS_PER_LONG == 64) + case 8: +@@ -187,7 +187,7 @@ + return __sync_add_and_fetch_2(addr, val); + #endif + case 4: +- return __sync_add_and_fetch_4(addr, val); ++ return __sync_add_and_fetch_4((uint32_t*) addr, val); + #if (CAA_BITS_PER_LONG == 64) + case 8: + return __sync_add_and_fetch_8(addr, val); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/armel/sources.list.jessie dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/armel/sources.list.jessie --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/armel/sources.list.jessie 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/armel/sources.list.jessie 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,3 @@ +# Debian (jessie) # Stable +deb http://ftp.debian.org/debian/ jessie main contrib non-free +deb-src http://ftp.debian.org/debian/ jessie main contrib non-free diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/armel/tizen/tizen.patch dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/armel/tizen/tizen.patch --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/armel/tizen/tizen.patch 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/armel/tizen/tizen.patch 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so +--- a/usr/lib/libc.so 2016-12-30 23:00:08.284951863 +0900 ++++ b/usr/lib/libc.so 2016-12-30 23:00:32.140951815 +0900 +@@ -2,4 +2,4 @@ + Use the shared library, but some functions are only in + the static library, so try that secondarily. */ + OUTPUT_FORMAT(elf32-littlearm) +-GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /lib/ld-linux.so.3 ) ) ++GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux.so.3 ) ) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/armv6/sources.list.buster dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/armv6/sources.list.buster --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/armv6/sources.list.buster 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/armv6/sources.list.buster 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,2 @@ +deb http://raspbian.raspberrypi.org/raspbian/ buster main contrib non-free rpi +deb-src http://raspbian.raspberrypi.org/raspbian/ buster main contrib non-free rpi diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/build-android-rootfs.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/build-android-rootfs.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/build-android-rootfs.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/build-android-rootfs.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,131 @@ +#!/usr/bin/env bash +set -e +__NDK_Version=r21 + +usage() +{ + echo "Creates a toolchain and sysroot used for cross-compiling for Android." + echo. + echo "Usage: $0 [BuildArch] [ApiLevel]" + echo. + echo "BuildArch is the target architecture of Android. Currently only arm64 is supported." + echo "ApiLevel is the target Android API level. API levels usually match to Android releases. See https://source.android.com/source/build-numbers.html" + echo. + echo "By default, the toolchain and sysroot will be generated in cross/android-rootfs/toolchain/[BuildArch]. You can change this behavior" + echo "by setting the TOOLCHAIN_DIR environment variable" + echo. + echo "By default, the NDK will be downloaded into the cross/android-rootfs/android-ndk-$__NDK_Version directory. If you already have an NDK installation," + echo "you can set the NDK_DIR environment variable to have this script use that installation of the NDK." + echo "By default, this script will generate a file, android_platform, in the root of the ROOTFS_DIR directory that contains the RID for the supported and tested Android build: android.28-arm64. This file is to replace '/etc/os-release', which is not available for Android." + exit 1 +} + +__ApiLevel=28 # The minimum platform for arm64 is API level 21 but the minimum version that support glob(3) is 28. See $ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/glob.h +__BuildArch=arm64 +__AndroidArch=aarch64 +__AndroidToolchain=aarch64-linux-android + +for i in "$@" + do + lowerI="$(echo $i | tr "[:upper:]" "[:lower:]")" + case $lowerI in + -?|-h|--help) + usage + exit 1 + ;; + arm64) + __BuildArch=arm64 + __AndroidArch=aarch64 + __AndroidToolchain=aarch64-linux-android + ;; + arm) + __BuildArch=arm + __AndroidArch=arm + __AndroidToolchain=arm-linux-androideabi + ;; + *[0-9]) + __ApiLevel=$i + ;; + *) + __UnprocessedBuildArgs="$__UnprocessedBuildArgs $i" + ;; + esac +done + +# Obtain the location of the bash script to figure out where the root of the repo is. +__ScriptBaseDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +__CrossDir="$__ScriptBaseDir/../../../.tools/android-rootfs" + +if [[ ! -f "$__CrossDir" ]]; then + mkdir -p "$__CrossDir" +fi + +# Resolve absolute path to avoid `../` in build logs +__CrossDir="$( cd "$__CrossDir" && pwd )" + +__NDK_Dir="$__CrossDir/android-ndk-$__NDK_Version" +__lldb_Dir="$__CrossDir/lldb" +__ToolchainDir="$__CrossDir/android-ndk-$__NDK_Version" + +if [[ -n "$TOOLCHAIN_DIR" ]]; then + __ToolchainDir=$TOOLCHAIN_DIR +fi + +if [[ -n "$NDK_DIR" ]]; then + __NDK_Dir=$NDK_DIR +fi + +echo "Target API level: $__ApiLevel" +echo "Target architecture: $__BuildArch" +echo "NDK location: $__NDK_Dir" +echo "Target Toolchain location: $__ToolchainDir" + +# Download the NDK if required +if [ ! -d $__NDK_Dir ]; then + echo Downloading the NDK into $__NDK_Dir + mkdir -p $__NDK_Dir + wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/android-ndk-$__NDK_Version-linux-x86_64.zip -O $__CrossDir/android-ndk-$__NDK_Version-linux-x86_64.zip + unzip -q $__CrossDir/android-ndk-$__NDK_Version-linux-x86_64.zip -d $__CrossDir +fi + +if [ ! -d $__lldb_Dir ]; then + mkdir -p $__lldb_Dir + echo Downloading LLDB into $__lldb_Dir + wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/lldb-2.3.3614996-linux-x86_64.zip -O $__CrossDir/lldb-2.3.3614996-linux-x86_64.zip + unzip -q $__CrossDir/lldb-2.3.3614996-linux-x86_64.zip -d $__lldb_Dir +fi + +echo "Download dependencies..." +__TmpDir=$__CrossDir/tmp/$__BuildArch/ +mkdir -p "$__TmpDir" + +# combined dependencies for coreclr, installer and libraries +__AndroidPackages="libicu" +__AndroidPackages+=" libandroid-glob" +__AndroidPackages+=" liblzma" +__AndroidPackages+=" krb5" +__AndroidPackages+=" openssl" + +for path in $(wget -qO- https://packages.termux.dev/termux-main-21/dists/stable/main/binary-$__AndroidArch/Packages |\ + grep -A15 "Package: \(${__AndroidPackages// /\\|}\)" | grep -v "static\|tool" | grep Filename); do + + if [[ "$path" != "Filename:" ]]; then + echo "Working on: $path" + wget -qO- https://packages.termux.dev/termux-main-21/$path | dpkg -x - "$__TmpDir" + fi +done + +cp -R "$__TmpDir/data/data/com.termux/files/usr/"* "$__ToolchainDir/sysroot/usr/" + +# Generate platform file for build.sh script to assign to __DistroRid +echo "Generating platform file..." +echo "RID=android.${__ApiLevel}-${__BuildArch}" > $__ToolchainDir/sysroot/android_platform + +echo "Now to build coreclr, libraries and installers; run:" +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory coreclr +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory libraries +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory installer diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/build-rootfs.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/build-rootfs.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/build-rootfs.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/build-rootfs.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,648 @@ +#!/usr/bin/env bash + +set -e + +usage() +{ + echo "Usage: $0 [BuildArch] [CodeName] [lldbx.y] [llvmx[.y]] [--skipunmount] --rootfsdir ]" + echo "BuildArch can be: arm(default), arm64, armel, armv6, ppc64le, riscv64, s390x, x64, x86" + echo "CodeName - optional, Code name for Linux, can be: xenial(default), zesty, bionic, alpine" + echo " for alpine can be specified with version: alpineX.YY or alpineedge" + echo " for FreeBSD can be: freebsd12, freebsd13" + echo " for illumos can be: illumos" + echo " for Haiku can be: haiku." + echo "lldbx.y - optional, LLDB version, can be: lldb3.9(default), lldb4.0, lldb5.0, lldb6.0 no-lldb. Ignored for alpine and FreeBSD" + echo "llvmx[.y] - optional, LLVM version for LLVM related packages." + echo "--skipunmount - optional, will skip the unmount of rootfs folder." + echo "--skipsigcheck - optional, will skip package signature checks (allowing untrusted packages)." + echo "--use-mirror - optional, use mirror URL to fetch resources, when available." + echo "--jobs N - optional, restrict to N jobs." + exit 1 +} + +__CodeName=xenial +__CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +__BuildArch=arm +__AlpineArch=armv7 +__FreeBSDArch=arm +__FreeBSDMachineArch=armv7 +__IllumosArch=arm7 +__HaikuArch=arm +__QEMUArch=arm +__UbuntuArch=armhf +__UbuntuRepo="http://ports.ubuntu.com/" +__LLDB_Package="liblldb-3.9-dev" +__SkipUnmount=0 + +# base development support +__UbuntuPackages="build-essential" + +__AlpinePackages="alpine-base" +__AlpinePackages+=" build-base" +__AlpinePackages+=" linux-headers" +__AlpinePackages+=" lldb-dev" +__AlpinePackages+=" python3" +__AlpinePackages+=" libedit" + +# symlinks fixer +__UbuntuPackages+=" symlinks" + +# runtime dependencies +__UbuntuPackages+=" libicu-dev" +__UbuntuPackages+=" liblttng-ust-dev" +__UbuntuPackages+=" libunwind8-dev" +__UbuntuPackages+=" libnuma-dev" + +__AlpinePackages+=" gettext-dev" +__AlpinePackages+=" icu-dev" +__AlpinePackages+=" libunwind-dev" +__AlpinePackages+=" lttng-ust-dev" +__AlpinePackages+=" compiler-rt" +__AlpinePackages+=" numactl-dev" + +# runtime libraries' dependencies +__UbuntuPackages+=" libcurl4-openssl-dev" +__UbuntuPackages+=" libkrb5-dev" +__UbuntuPackages+=" libssl-dev" +__UbuntuPackages+=" zlib1g-dev" + +__AlpinePackages+=" curl-dev" +__AlpinePackages+=" krb5-dev" +__AlpinePackages+=" openssl-dev" +__AlpinePackages+=" zlib-dev" + +__FreeBSDBase="12.4-RELEASE" +__FreeBSDPkg="1.17.0" +__FreeBSDABI="12" +__FreeBSDPackages="libunwind" +__FreeBSDPackages+=" icu" +__FreeBSDPackages+=" libinotify" +__FreeBSDPackages+=" openssl" +__FreeBSDPackages+=" krb5" +__FreeBSDPackages+=" terminfo-db" + +__IllumosPackages="icu" +__IllumosPackages+=" mit-krb5" +__IllumosPackages+=" openssl" +__IllumosPackages+=" zlib" + +__HaikuPackages="gcc_syslibs" +__HaikuPackages+=" gcc_syslibs_devel" +__HaikuPackages+=" gmp" +__HaikuPackages+=" gmp_devel" +__HaikuPackages+=" icu66" +__HaikuPackages+=" icu66_devel" +__HaikuPackages+=" krb5" +__HaikuPackages+=" krb5_devel" +__HaikuPackages+=" libiconv" +__HaikuPackages+=" libiconv_devel" +__HaikuPackages+=" llvm12_libunwind" +__HaikuPackages+=" llvm12_libunwind_devel" +__HaikuPackages+=" mpfr" +__HaikuPackages+=" mpfr_devel" +__HaikuPackages+=" openssl" +__HaikuPackages+=" openssl_devel" +__HaikuPackages+=" zlib" +__HaikuPackages+=" zlib_devel" + +# ML.NET dependencies +__UbuntuPackages+=" libomp5" +__UbuntuPackages+=" libomp-dev" + +# Taken from https://github.com/alpinelinux/alpine-chroot-install/blob/6d08f12a8a70dd9b9dc7d997c88aa7789cc03c42/alpine-chroot-install#L85-L133 +__AlpineKeys=' +4a6a0840:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1yHJxQgsHQREclQu4Ohe\nqxTxd1tHcNnvnQTu/UrTky8wWvgXT+jpveroeWWnzmsYlDI93eLI2ORakxb3gA2O\nQ0Ry4ws8vhaxLQGC74uQR5+/yYrLuTKydFzuPaS1dK19qJPXB8GMdmFOijnXX4SA\njixuHLe1WW7kZVtjL7nufvpXkWBGjsfrvskdNA/5MfxAeBbqPgaq0QMEfxMAn6/R\nL5kNepi/Vr4S39Xvf2DzWkTLEK8pcnjNkt9/aafhWqFVW7m3HCAII6h/qlQNQKSo\nGuH34Q8GsFG30izUENV9avY7hSLq7nggsvknlNBZtFUcmGoQrtx3FmyYsIC8/R+B\nywIDAQAB +5243ef4b:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvNijDxJ8kloskKQpJdx+\nmTMVFFUGDoDCbulnhZMJoKNkSuZOzBoFC94omYPtxnIcBdWBGnrm6ncbKRlR+6oy\nDO0W7c44uHKCFGFqBhDasdI4RCYP+fcIX/lyMh6MLbOxqS22TwSLhCVjTyJeeH7K\naA7vqk+QSsF4TGbYzQDDpg7+6aAcNzg6InNePaywA6hbT0JXbxnDWsB+2/LLSF2G\nmnhJlJrWB1WGjkz23ONIWk85W4S0XB/ewDefd4Ly/zyIciastA7Zqnh7p3Ody6Q0\nsS2MJzo7p3os1smGjUF158s6m/JbVh4DN6YIsxwl2OjDOz9R0OycfJSDaBVIGZzg\ncQIDAQAB +524d27bb:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr8s1q88XpuJWLCZALdKj\nlN8wg2ePB2T9aIcaxryYE/Jkmtu+ZQ5zKq6BT3y/udt5jAsMrhHTwroOjIsF9DeG\ne8Y3vjz+Hh4L8a7hZDaw8jy3CPag47L7nsZFwQOIo2Cl1SnzUc6/owoyjRU7ab0p\niWG5HK8IfiybRbZxnEbNAfT4R53hyI6z5FhyXGS2Ld8zCoU/R4E1P0CUuXKEN4p0\n64dyeUoOLXEWHjgKiU1mElIQj3k/IF02W89gDj285YgwqA49deLUM7QOd53QLnx+\nxrIrPv3A+eyXMFgexNwCKQU9ZdmWa00MjjHlegSGK8Y2NPnRoXhzqSP9T9i2HiXL\nVQIDAQAB +5261cecb:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwlzMkl7b5PBdfMzGdCT0\ncGloRr5xGgVmsdq5EtJvFkFAiN8Ac9MCFy/vAFmS8/7ZaGOXoCDWbYVLTLOO2qtX\nyHRl+7fJVh2N6qrDDFPmdgCi8NaE+3rITWXGrrQ1spJ0B6HIzTDNEjRKnD4xyg4j\ng01FMcJTU6E+V2JBY45CKN9dWr1JDM/nei/Pf0byBJlMp/mSSfjodykmz4Oe13xB\nCa1WTwgFykKYthoLGYrmo+LKIGpMoeEbY1kuUe04UiDe47l6Oggwnl+8XD1MeRWY\nsWgj8sF4dTcSfCMavK4zHRFFQbGp/YFJ/Ww6U9lA3Vq0wyEI6MCMQnoSMFwrbgZw\nwwIDAQAB +58199dcc:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3v8/ye/V/t5xf4JiXLXa\nhWFRozsnmn3hobON20GdmkrzKzO/eUqPOKTpg2GtvBhK30fu5oY5uN2ORiv2Y2ht\neLiZ9HVz3XP8Fm9frha60B7KNu66FO5P2o3i+E+DWTPqqPcCG6t4Znk2BypILcit\nwiPKTsgbBQR2qo/cO01eLLdt6oOzAaF94NH0656kvRewdo6HG4urbO46tCAizvCR\nCA7KGFMyad8WdKkTjxh8YLDLoOCtoZmXmQAiwfRe9pKXRH/XXGop8SYptLqyVVQ+\ntegOD9wRs2tOlgcLx4F/uMzHN7uoho6okBPiifRX+Pf38Vx+ozXh056tjmdZkCaV\naQIDAQAB +58cbb476:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoSPnuAGKtRIS5fEgYPXD\n8pSGvKAmIv3A08LBViDUe+YwhilSHbYXUEAcSH1KZvOo1WT1x2FNEPBEFEFU1Eyc\n+qGzbA03UFgBNvArurHQ5Z/GngGqE7IarSQFSoqewYRtFSfp+TL9CUNBvM0rT7vz\n2eMu3/wWG+CBmb92lkmyWwC1WSWFKO3x8w+Br2IFWvAZqHRt8oiG5QtYvcZL6jym\nY8T6sgdDlj+Y+wWaLHs9Fc+7vBuyK9C4O1ORdMPW15qVSl4Lc2Wu1QVwRiKnmA+c\nDsH/m7kDNRHM7TjWnuj+nrBOKAHzYquiu5iB3Qmx+0gwnrSVf27Arc3ozUmmJbLj\nzQIDAQAB +58e4f17d:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvBxJN9ErBgdRcPr5g4hV\nqyUSGZEKuvQliq2Z9SRHLh2J43+EdB6A+yzVvLnzcHVpBJ+BZ9RV30EM9guck9sh\nr+bryZcRHyjG2wiIEoduxF2a8KeWeQH7QlpwGhuobo1+gA8L0AGImiA6UP3LOirl\nI0G2+iaKZowME8/tydww4jx5vG132JCOScMjTalRsYZYJcjFbebQQolpqRaGB4iG\nWqhytWQGWuKiB1A22wjmIYf3t96l1Mp+FmM2URPxD1gk/BIBnX7ew+2gWppXOK9j\n1BJpo0/HaX5XoZ/uMqISAAtgHZAqq+g3IUPouxTphgYQRTRYpz2COw3NF43VYQrR\nbQIDAQAB +60ac2099:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwR4uJVtJOnOFGchnMW5Y\nj5/waBdG1u5BTMlH+iQMcV5+VgWhmpZHJCBz3ocD+0IGk2I68S5TDOHec/GSC0lv\n6R9o6F7h429GmgPgVKQsc8mPTPtbjJMuLLs4xKc+viCplXc0Nc0ZoHmCH4da6fCV\ntdpHQjVe6F9zjdquZ4RjV6R6JTiN9v924dGMAkbW/xXmamtz51FzondKC52Gh8Mo\n/oA0/T0KsCMCi7tb4QNQUYrf+Xcha9uus4ww1kWNZyfXJB87a2kORLiWMfs2IBBJ\nTmZ2Fnk0JnHDb8Oknxd9PvJPT0mvyT8DA+KIAPqNvOjUXP4bnjEHJcoCP9S5HkGC\nIQIDAQAB +6165ee59:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAutQkua2CAig4VFSJ7v54\nALyu/J1WB3oni7qwCZD3veURw7HxpNAj9hR+S5N/pNeZgubQvJWyaPuQDm7PTs1+\ntFGiYNfAsiibX6Rv0wci3M+z2XEVAeR9Vzg6v4qoofDyoTbovn2LztaNEjTkB+oK\ntlvpNhg1zhou0jDVYFniEXvzjckxswHVb8cT0OMTKHALyLPrPOJzVtM9C1ew2Nnc\n3848xLiApMu3NBk0JqfcS3Bo5Y2b1FRVBvdt+2gFoKZix1MnZdAEZ8xQzL/a0YS5\nHd0wj5+EEKHfOd3A75uPa/WQmA+o0cBFfrzm69QDcSJSwGpzWrD1ScH3AK8nWvoj\nv7e9gukK/9yl1b4fQQ00vttwJPSgm9EnfPHLAtgXkRloI27H6/PuLoNvSAMQwuCD\nhQRlyGLPBETKkHeodfLoULjhDi1K2gKJTMhtbnUcAA7nEphkMhPWkBpgFdrH+5z4\nLxy+3ek0cqcI7K68EtrffU8jtUj9LFTUC8dERaIBs7NgQ/LfDbDfGh9g6qVj1hZl\nk9aaIPTm/xsi8v3u+0qaq7KzIBc9s59JOoA8TlpOaYdVgSQhHHLBaahOuAigH+VI\nisbC9vmqsThF2QdDtQt37keuqoda2E6sL7PUvIyVXDRfwX7uMDjlzTxHTymvq2Ck\nhtBqojBnThmjJQFgZXocHG8CAwEAAQ== +61666e3f:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlEyxkHggKCXC2Wf5Mzx4\nnZLFZvU2bgcA3exfNPO/g1YunKfQY+Jg4fr6tJUUTZ3XZUrhmLNWvpvSwDS19ZmC\nIXOu0+V94aNgnhMsk9rr59I8qcbsQGIBoHzuAl8NzZCgdbEXkiY90w1skUw8J57z\nqCsMBydAueMXuWqF5nGtYbi5vHwK42PffpiZ7G5Kjwn8nYMW5IZdL6ZnMEVJUWC9\nI4waeKg0yskczYDmZUEAtrn3laX9677ToCpiKrvmZYjlGl0BaGp3cxggP2xaDbUq\nqfFxWNgvUAb3pXD09JM6Mt6HSIJaFc9vQbrKB9KT515y763j5CC2KUsilszKi3mB\nHYe5PoebdjS7D1Oh+tRqfegU2IImzSwW3iwA7PJvefFuc/kNIijfS/gH/cAqAK6z\nbhdOtE/zc7TtqW2Wn5Y03jIZdtm12CxSxwgtCF1NPyEWyIxAQUX9ACb3M0FAZ61n\nfpPrvwTaIIxxZ01L3IzPLpbc44x/DhJIEU+iDt6IMTrHOphD9MCG4631eIdB0H1b\n6zbNX1CXTsafqHRFV9XmYYIeOMggmd90s3xIbEujA6HKNP/gwzO6CDJ+nHFDEqoF\nSkxRdTkEqjTjVKieURW7Swv7zpfu5PrsrrkyGnsRrBJJzXlm2FOOxnbI2iSL1B5F\nrO5kbUxFeZUIDq+7Yv4kLWcCAwEAAQ== +616a9724:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnC+bR4bHf/L6QdU4puhQ\ngl1MHePszRC38bzvVFDUJsmCaMCL2suCs2A2yxAgGb9pu9AJYLAmxQC4mM3jNqhg\n/E7yuaBbek3O02zN/ctvflJ250wZCy+z0ZGIp1ak6pu1j14IwHokl9j36zNfGtfv\nADVOcdpWITFFlPqwq1qt/H3UsKVmtiF3BNWWTeUEQwKvlU8ymxgS99yn0+4OPyNT\nL3EUeS+NQJtDS01unau0t7LnjUXn+XIneWny8bIYOQCuVR6s/gpIGuhBaUqwaJOw\n7jkJZYF2Ij7uPb4b5/R3vX2FfxxqEHqssFSg8FFUNTZz3qNZs0CRVyfA972g9WkJ\nhPfn31pQYil4QGRibCMIeU27YAEjXoqfJKEPh4UWMQsQLrEfdGfb8VgwrPbniGfU\nL3jKJR3VAafL9330iawzVQDlIlwGl6u77gEXMl9K0pfazunYhAp+BMP+9ot5ckK+\nosmrqj11qMESsAj083GeFdfV3pXEIwUytaB0AKEht9DbqUfiE/oeZ/LAXgySMtVC\nsbC4ESmgVeY2xSBIJdDyUap7FR49GGrw0W49NUv9gRgQtGGaNVQQO9oGL2PBC41P\niWF9GLoX30HIz1P8PF/cZvicSSPkQf2Z6TV+t0ebdGNS5DjapdnCrq8m9Z0pyKsQ\nuxAL2a7zX8l5i1CZh1ycUGsCAwEAAQ== +616abc23:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0MfCDrhODRCIxR9Dep1s\neXafh5CE5BrF4WbCgCsevyPIdvTeyIaW4vmO3bbG4VzhogDZju+R3IQYFuhoXP5v\nY+zYJGnwrgz3r5wYAvPnLEs1+dtDKYOgJXQj+wLJBW1mzRDL8FoRXOe5iRmn1EFS\nwZ1DoUvyu7/J5r0itKicZp3QKED6YoilXed+1vnS4Sk0mzN4smuMR9eO1mMCqNp9\n9KTfRDHTbakIHwasECCXCp50uXdoW6ig/xUAFanpm9LtK6jctNDbXDhQmgvAaLXZ\nLvFqoaYJ/CvWkyYCgL6qxvMvVmPoRv7OPcyni4xR/WgWa0MSaEWjgPx3+yj9fiMA\n1S02pFWFDOr5OUF/O4YhFJvUCOtVsUPPfA/Lj6faL0h5QI9mQhy5Zb9TTaS9jB6p\nLw7u0dJlrjFedk8KTJdFCcaGYHP6kNPnOxMylcB/5WcztXZVQD5WpCicGNBxCGMm\nW64SgrV7M07gQfL/32QLsdqPUf0i8hoVD8wfQ3EpbQzv6Fk1Cn90bZqZafg8XWGY\nwddhkXk7egrr23Djv37V2okjzdqoyLBYBxMz63qQzFoAVv5VoY2NDTbXYUYytOvG\nGJ1afYDRVWrExCech1mX5ZVUB1br6WM+psFLJFoBFl6mDmiYt0vMYBddKISsvwLl\nIJQkzDwtXzT2cSjoj3T5QekCAwEAAQ== +616ac3bc:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvaaoSLab+IluixwKV5Od\n0gib2YurjPatGIbn5Ov2DLUFYiebj2oJINXJSwUOO+4WcuHFEqiL/1rya+k5hLZt\nhnPL1tn6QD4rESznvGSasRCQNT2vS/oyZbTYJRyAtFkEYLlq0t3S3xBxxHWuvIf0\nqVxVNYpQWyM3N9RIeYBR/euXKJXileSHk/uq1I5wTC0XBIHWcthczGN0m9wBEiWS\n0m3cnPk4q0Ea8mUJ91Rqob19qETz6VbSPYYpZk3qOycjKosuwcuzoMpwU8KRiMFd\n5LHtX0Hx85ghGsWDVtS0c0+aJa4lOMGvJCAOvDfqvODv7gKlCXUpgumGpLdTmaZ8\n1RwqspAe3IqBcdKTqRD4m2mSg23nVx2FAY3cjFvZQtfooT7q1ItRV5RgH6FhQSl7\n+6YIMJ1Bf8AAlLdRLpg+doOUGcEn+pkDiHFgI8ylH1LKyFKw+eXaAml/7DaWZk1d\ndqggwhXOhc/UUZFQuQQ8A8zpA13PcbC05XxN2hyP93tCEtyynMLVPtrRwDnHxFKa\nqKzs3rMDXPSXRn3ZZTdKH3069ApkEjQdpcwUh+EmJ1Ve/5cdtzT6kKWCjKBFZP/s\n91MlRrX2BTRdHaU5QJkUheUtakwxuHrdah2F94lRmsnQlpPr2YseJu6sIE+Dnx4M\nCfhdVbQL2w54R645nlnohu8CAwEAAQ== +616adfeb:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq0BFD1D4lIxQcsqEpQzU\npNCYM3aP1V/fxxVdT4DWvSI53JHTwHQamKdMWtEXetWVbP5zSROniYKFXd/xrD9X\n0jiGHey3lEtylXRIPxe5s+wXoCmNLcJVnvTcDtwx/ne2NLHxp76lyc25At+6RgE6\nADjLVuoD7M4IFDkAsd8UQ8zM0Dww9SylIk/wgV3ZkifecvgUQRagrNUdUjR56EBZ\nraQrev4hhzOgwelT0kXCu3snbUuNY/lU53CoTzfBJ5UfEJ5pMw1ij6X0r5S9IVsy\nKLWH1hiO0NzU2c8ViUYCly4Fe9xMTFc6u2dy/dxf6FwERfGzETQxqZvSfrRX+GLj\n/QZAXiPg5178hT/m0Y3z5IGenIC/80Z9NCi+byF1WuJlzKjDcF/TU72zk0+PNM/H\nKuppf3JT4DyjiVzNC5YoWJT2QRMS9KLP5iKCSThwVceEEg5HfhQBRT9M6KIcFLSs\nmFjx9kNEEmc1E8hl5IR3+3Ry8G5/bTIIruz14jgeY9u5jhL8Vyyvo41jgt9sLHR1\n/J1TxKfkgksYev7PoX6/ZzJ1ksWKZY5NFoDXTNYUgzFUTOoEaOg3BAQKadb3Qbbq\nXIrxmPBdgrn9QI7NCgfnAY3Tb4EEjs3ON/BNyEhUENcXOH6I1NbcuBQ7g9P73kE4\nVORdoc8MdJ5eoKBpO8Ww8HECAwEAAQ== +616ae350:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyduVzi1mWm+lYo2Tqt/0\nXkCIWrDNP1QBMVPrE0/ZlU2bCGSoo2Z9FHQKz/mTyMRlhNqTfhJ5qU3U9XlyGOPJ\npiM+b91g26pnpXJ2Q2kOypSgOMOPA4cQ42PkHBEqhuzssfj9t7x47ppS94bboh46\nxLSDRff/NAbtwTpvhStV3URYkxFG++cKGGa5MPXBrxIp+iZf9GnuxVdST5PGiVGP\nODL/b69sPJQNbJHVquqUTOh5Ry8uuD2WZuXfKf7/C0jC/ie9m2+0CttNu9tMciGM\nEyKG1/Xhk5iIWO43m4SrrT2WkFlcZ1z2JSf9Pjm4C2+HovYpihwwdM/OdP8Xmsnr\nDzVB4YvQiW+IHBjStHVuyiZWc+JsgEPJzisNY0Wyc/kNyNtqVKpX6dRhMLanLmy+\nf53cCSI05KPQAcGj6tdL+D60uKDkt+FsDa0BTAobZ31OsFVid0vCXtsbplNhW1IF\nHwsGXBTVcfXg44RLyL8Lk/2dQxDHNHzAUslJXzPxaHBLmt++2COa2EI1iWlvtznk\nOk9WP8SOAIj+xdqoiHcC4j72BOVVgiITIJNHrbppZCq6qPR+fgXmXa+sDcGh30m6\n9Wpbr28kLMSHiENCWTdsFij+NQTd5S47H7XTROHnalYDuF1RpS+DpQidT5tUimaT\nJZDr++FjKrnnijbyNF8b98UCAwEAAQ== +616db30d:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnpUpyWDWjlUk3smlWeA0\nlIMW+oJ38t92CRLHH3IqRhyECBRW0d0aRGtq7TY8PmxjjvBZrxTNDpJT6KUk4LRm\na6A6IuAI7QnNK8SJqM0DLzlpygd7GJf8ZL9SoHSH+gFsYF67Cpooz/YDqWrlN7Vw\ntO00s0B+eXy+PCXYU7VSfuWFGK8TGEv6HfGMALLjhqMManyvfp8hz3ubN1rK3c8C\nUS/ilRh1qckdbtPvoDPhSbTDmfU1g/EfRSIEXBrIMLg9ka/XB9PvWRrekrppnQzP\nhP9YE3x/wbFc5QqQWiRCYyQl/rgIMOXvIxhkfe8H5n1Et4VAorkpEAXdsfN8KSVv\nLSMazVlLp9GYq5SUpqYX3KnxdWBgN7BJoZ4sltsTpHQ/34SXWfu3UmyUveWj7wp0\nx9hwsPirVI00EEea9AbP7NM2rAyu6ukcm4m6ATd2DZJIViq2es6m60AE6SMCmrQF\nwmk4H/kdQgeAELVfGOm2VyJ3z69fQuywz7xu27S6zTKi05Qlnohxol4wVb6OB7qG\nLPRtK9ObgzRo/OPumyXqlzAi/Yvyd1ZQk8labZps3e16bQp8+pVPiumWioMFJDWV\nGZjCmyMSU8V6MB6njbgLHoyg2LCukCAeSjbPGGGYhnKLm1AKSoJh3IpZuqcKCk5C\n8CM1S15HxV78s9dFntEqIokCAwEAAQ== +' +__Keyring= +__SkipSigCheck=0 +__UseMirror=0 + +__UnprocessedBuildArgs= +while :; do + if [[ "$#" -le 0 ]]; then + break + fi + + lowerI="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + case $lowerI in + -\?|-h|--help) + usage + exit 1 + ;; + arm) + __BuildArch=arm + __UbuntuArch=armhf + __AlpineArch=armv7 + __QEMUArch=arm + ;; + arm64) + __BuildArch=arm64 + __UbuntuArch=arm64 + __AlpineArch=aarch64 + __QEMUArch=aarch64 + __FreeBSDArch=arm64 + __FreeBSDMachineArch=aarch64 + ;; + armel) + __BuildArch=armel + __UbuntuArch=armel + __UbuntuRepo="http://ftp.debian.org/debian/" + __CodeName=jessie + ;; + armv6) + __BuildArch=armv6 + __UbuntuArch=armhf + __QEMUArch=arm + __UbuntuRepo="http://raspbian.raspberrypi.org/raspbian/" + __CodeName=buster + __LLDB_Package="liblldb-6.0-dev" + + if [[ -e "/usr/share/keyrings/raspbian-archive-keyring.gpg" ]]; then + __Keyring="--keyring /usr/share/keyrings/raspbian-archive-keyring.gpg" + fi + ;; + riscv64) + __BuildArch=riscv64 + __AlpineArch=riscv64 + __AlpinePackages="${__AlpinePackages// lldb-dev/}" + __QEMUArch=riscv64 + __UbuntuArch=riscv64 + __UbuntuRepo="http://deb.debian.org/debian-ports" + __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" + unset __LLDB_Package + + if [[ -e "/usr/share/keyrings/debian-ports-archive-keyring.gpg" ]]; then + __Keyring="--keyring /usr/share/keyrings/debian-ports-archive-keyring.gpg --include=debian-ports-archive-keyring" + fi + ;; + ppc64le) + __BuildArch=ppc64le + __AlpineArch=ppc64le + __QEMUArch=ppc64le + __UbuntuArch=ppc64el + __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/" + __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" + __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" + __UbuntuPackages="${__UbuntuPackages// libomp5/}" + unset __LLDB_Package + ;; + s390x) + __BuildArch=s390x + __AlpineArch=s390x + __QEMUArch=s390x + __UbuntuArch=s390x + __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/" + __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" + __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" + __UbuntuPackages="${__UbuntuPackages// libomp5/}" + unset __LLDB_Package + ;; + x64) + __BuildArch=x64 + __AlpineArch=x86_64 + __UbuntuArch=amd64 + __FreeBSDArch=amd64 + __FreeBSDMachineArch=amd64 + __illumosArch=x86_64 + __HaikuArch=x86_64 + __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" + ;; + x86) + __BuildArch=x86 + __UbuntuArch=i386 + __AlpineArch=x86 + __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" + ;; + lldb*) + version="${lowerI/lldb/}" + parts=(${version//./ }) + + # for versions > 6.0, lldb has dropped the minor version + if [[ "${parts[0]}" -gt 6 ]]; then + version="${parts[0]}" + fi + + __LLDB_Package="liblldb-${version}-dev" + ;; + no-lldb) + unset __LLDB_Package + ;; + llvm*) + version="${lowerI/llvm/}" + parts=(${version//./ }) + __LLVM_MajorVersion="${parts[0]}" + __LLVM_MinorVersion="${parts[1]}" + + # for versions > 6.0, llvm has dropped the minor version + if [[ -z "$__LLVM_MinorVersion" && "$__LLVM_MajorVersion" -le 6 ]]; then + __LLVM_MinorVersion=0; + fi + ;; + xenial) # Ubuntu 16.04 + if [[ "$__CodeName" != "jessie" ]]; then + __CodeName=xenial + fi + ;; + zesty) # Ubuntu 17.04 + if [[ "$__CodeName" != "jessie" ]]; then + __CodeName=zesty + fi + ;; + bionic) # Ubuntu 18.04 + if [[ "$__CodeName" != "jessie" ]]; then + __CodeName=bionic + fi + ;; + focal) # Ubuntu 20.04 + if [[ "$__CodeName" != "jessie" ]]; then + __CodeName=focal + fi + ;; + jammy) # Ubuntu 22.04 + if [[ "$__CodeName" != "jessie" ]]; then + __CodeName=jammy + fi + ;; + jessie) # Debian 8 + __CodeName=jessie + + if [[ -z "$__UbuntuRepo" ]]; then + __UbuntuRepo="http://ftp.debian.org/debian/" + fi + ;; + stretch) # Debian 9 + __CodeName=stretch + __LLDB_Package="liblldb-6.0-dev" + + if [[ -z "$__UbuntuRepo" ]]; then + __UbuntuRepo="http://ftp.debian.org/debian/" + fi + ;; + buster) # Debian 10 + __CodeName=buster + __LLDB_Package="liblldb-6.0-dev" + + if [[ -z "$__UbuntuRepo" ]]; then + __UbuntuRepo="http://ftp.debian.org/debian/" + fi + ;; + bullseye) # Debian 11 + __CodeName=bullseye + + if [[ -z "$__UbuntuRepo" ]]; then + __UbuntuRepo="http://ftp.debian.org/debian/" + fi + ;; + sid) # Debian sid + __CodeName=sid + + if [[ -z "$__UbuntuRepo" ]]; then + __UbuntuRepo="http://ftp.debian.org/debian/" + fi + ;; + tizen) + __CodeName= + __UbuntuRepo= + __Tizen=tizen + ;; + alpine*) + __CodeName=alpine + __UbuntuRepo= + version="${lowerI/alpine/}" + + if [[ "$version" == "edge" ]]; then + __AlpineVersion=edge + else + parts=(${version//./ }) + __AlpineMajorVersion="${parts[0]}" + __AlpineMinoVersion="${parts[1]}" + __AlpineVersion="$__AlpineMajorVersion.$__AlpineMinoVersion" + fi + ;; + freebsd12) + __CodeName=freebsd + __SkipUnmount=1 + ;; + freebsd13) + __CodeName=freebsd + __FreeBSDBase="13.2-RELEASE" + __FreeBSDABI="13" + __SkipUnmount=1 + ;; + illumos) + __CodeName=illumos + __SkipUnmount=1 + ;; + haiku) + __CodeName=haiku + __SkipUnmount=1 + ;; + --skipunmount) + __SkipUnmount=1 + ;; + --skipsigcheck) + __SkipSigCheck=1 + ;; + --rootfsdir|-rootfsdir) + shift + __RootfsDir="$1" + ;; + --use-mirror) + __UseMirror=1 + ;; + --use-jobs) + shift + MAXJOBS=$1 + ;; + *) + __UnprocessedBuildArgs="$__UnprocessedBuildArgs $1" + ;; + esac + + shift +done + +case "$__AlpineVersion" in + 3.14) __AlpinePackages+=" llvm11-libs" ;; + 3.15) __AlpinePackages+=" llvm12-libs" ;; + 3.16) __AlpinePackages+=" llvm13-libs" ;; + 3.17) __AlpinePackages+=" llvm15-libs" ;; + edge) __AlpineLlvmLibsLookup=1 ;; + *) + if [[ "$__AlpineArch" =~ s390x|ppc64le ]]; then + __AlpineVersion=3.15 # minimum version that supports lldb-dev + __AlpinePackages+=" llvm12-libs" + elif [[ "$__AlpineArch" == "x86" ]]; then + __AlpineVersion=3.17 # minimum version that supports lldb-dev + __AlpinePackages+=" llvm15-libs" + elif [[ "$__AlpineArch" == "riscv64" ]]; then + __AlpineLlvmLibsLookup=1 + __AlpineVersion=edge # minimum version with APKINDEX.tar.gz (packages archive) + else + __AlpineVersion=3.13 # 3.13 to maximize compatibility + __AlpinePackages+=" llvm10-libs" + + if [[ "$__AlpineArch" == "armv7" ]]; then + __AlpinePackages="${__AlpinePackages//numactl-dev/}" + fi + fi +esac + +if [[ "$__AlpineVersion" =~ 3\.1[345] ]]; then + # compiler-rt--static was merged in compiler-rt package in alpine 3.16 + # for older versions, we need compiler-rt--static, so replace the name + __AlpinePackages="${__AlpinePackages/compiler-rt/compiler-rt-static}" +fi + +if [[ "$__BuildArch" == "armel" ]]; then + __LLDB_Package="lldb-3.5-dev" +fi + +if [[ "$__CodeName" == "xenial" && "$__UbuntuArch" == "armhf" ]]; then + # libnuma-dev is not available on armhf for xenial + __UbuntuPackages="${__UbuntuPackages//libnuma-dev/}" +fi + +__UbuntuPackages+=" ${__LLDB_Package:-}" + +if [[ -n "$__LLVM_MajorVersion" ]]; then + __UbuntuPackages+=" libclang-common-${__LLVM_MajorVersion}${__LLVM_MinorVersion:+.$__LLVM_MinorVersion}-dev" +fi + +if [[ -z "$__RootfsDir" && -n "$ROOTFS_DIR" ]]; then + __RootfsDir="$ROOTFS_DIR" +fi + +if [[ -z "$__RootfsDir" ]]; then + __RootfsDir="$__CrossDir/../../../.tools/rootfs/$__BuildArch" +fi + +if [[ -d "$__RootfsDir" ]]; then + if [[ "$__SkipUnmount" == "0" ]]; then + umount "$__RootfsDir"/* || true + fi + rm -rf "$__RootfsDir" +fi + +mkdir -p "$__RootfsDir" +__RootfsDir="$( cd "$__RootfsDir" && pwd )" + +if [[ "$__CodeName" == "alpine" ]]; then + __ApkToolsVersion=2.12.11 + __ApkToolsSHA512SUM=53e57b49230da07ef44ee0765b9592580308c407a8d4da7125550957bb72cb59638e04f8892a18b584451c8d841d1c7cb0f0ab680cc323a3015776affaa3be33 + __ApkToolsDir="$(mktemp -d)" + __ApkKeysDir="$(mktemp -d)" + + wget "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic//v$__ApkToolsVersion/x86_64/apk.static" -P "$__ApkToolsDir" + echo "$__ApkToolsSHA512SUM $__ApkToolsDir/apk.static" | sha512sum -c + chmod +x "$__ApkToolsDir/apk.static" + + if [[ -f "/usr/bin/qemu-$__QEMUArch-static" ]]; then + mkdir -p "$__RootfsDir"/usr/bin + cp -v "/usr/bin/qemu-$__QEMUArch-static" "$__RootfsDir/usr/bin" + fi + + if [[ "$__AlpineVersion" == "edge" ]]; then + version=edge + else + version="v$__AlpineVersion" + fi + + for line in $__AlpineKeys; do + id="${line%%:*}" + content="${line#*:}" + + echo -e "-----BEGIN PUBLIC KEY-----\n$content\n-----END PUBLIC KEY-----" > "$__ApkKeysDir/alpine-devel@lists.alpinelinux.org-$id.rsa.pub" + done + + if [[ "$__SkipSigCheck" == "1" ]]; then + __ApkSignatureArg="--allow-untrusted" + else + __ApkSignatureArg="--keys-dir $__ApkKeysDir" + fi + + # initialize DB + "$__ApkToolsDir/apk.static" \ + -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ + -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" --initdb add + + if [[ "$__AlpineLlvmLibsLookup" == 1 ]]; then + __AlpinePackages+=" $("$__ApkToolsDir/apk.static" \ + -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ + -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" \ + search 'llvm*-libs' | sort | tail -1 | sed 's/-[^-]*//2g')" + fi + + # install all packages in one go + "$__ApkToolsDir/apk.static" \ + -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ + -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" \ + add $__AlpinePackages + + rm -r "$__ApkToolsDir" +elif [[ "$__CodeName" == "freebsd" ]]; then + mkdir -p "$__RootfsDir"/usr/local/etc + JOBS=${MAXJOBS:="$(getconf _NPROCESSORS_ONLN)"} + wget -O - "https://download.freebsd.org/ftp/releases/${__FreeBSDArch}/${__FreeBSDMachineArch}/${__FreeBSDBase}/base.txz" | tar -C "$__RootfsDir" -Jxf - ./lib ./usr/lib ./usr/libdata ./usr/include ./usr/share/keys ./etc ./bin/freebsd-version + echo "ABI = \"FreeBSD:${__FreeBSDABI}:${__FreeBSDMachineArch}\"; FINGERPRINTS = \"${__RootfsDir}/usr/share/keys\"; REPOS_DIR = [\"${__RootfsDir}/etc/pkg\"]; REPO_AUTOUPDATE = NO; RUN_SCRIPTS = NO;" > "${__RootfsDir}"/usr/local/etc/pkg.conf + echo "FreeBSD: { url: \"pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly\", mirror_type: \"srv\", signature_type: \"fingerprints\", fingerprints: \"${__RootfsDir}/usr/share/keys/pkg\", enabled: yes }" > "${__RootfsDir}"/etc/pkg/FreeBSD.conf + mkdir -p "$__RootfsDir"/tmp + # get and build package manager + wget -O - "https://github.com/freebsd/pkg/archive/${__FreeBSDPkg}.tar.gz" | tar -C "$__RootfsDir"/tmp -zxf - + cd "$__RootfsDir/tmp/pkg-${__FreeBSDPkg}" + # needed for install to succeed + mkdir -p "$__RootfsDir"/host/etc + ./autogen.sh && ./configure --prefix="$__RootfsDir"/host && make -j "$JOBS" && make install + rm -rf "$__RootfsDir/tmp/pkg-${__FreeBSDPkg}" + # install packages we need. + INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf update + INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf install --yes $__FreeBSDPackages +elif [[ "$__CodeName" == "illumos" ]]; then + mkdir "$__RootfsDir/tmp" + pushd "$__RootfsDir/tmp" + JOBS=${MAXJOBS:="$(getconf _NPROCESSORS_ONLN)"} + echo "Downloading sysroot." + wget -O - https://github.com/illumos/sysroot/releases/download/20181213-de6af22ae73b-v1/illumos-sysroot-i386-20181213-de6af22ae73b-v1.tar.gz | tar -C "$__RootfsDir" -xzf - + echo "Building binutils. Please wait.." + wget -O - https://ftp.gnu.org/gnu/binutils/binutils-2.33.1.tar.bz2 | tar -xjf - + mkdir build-binutils && cd build-binutils + ../binutils-2.33.1/configure --prefix="$__RootfsDir" --target="${__illumosArch}-sun-solaris2.10" --program-prefix="${__illumosArch}-illumos-" --with-sysroot="$__RootfsDir" + make -j "$JOBS" && make install && cd .. + echo "Building gcc. Please wait.." + wget -O - https://ftp.gnu.org/gnu/gcc/gcc-8.4.0/gcc-8.4.0.tar.xz | tar -xJf - + CFLAGS="-fPIC" + CXXFLAGS="-fPIC" + CXXFLAGS_FOR_TARGET="-fPIC" + CFLAGS_FOR_TARGET="-fPIC" + export CFLAGS CXXFLAGS CXXFLAGS_FOR_TARGET CFLAGS_FOR_TARGET + mkdir build-gcc && cd build-gcc + ../gcc-8.4.0/configure --prefix="$__RootfsDir" --target="${__illumosArch}-sun-solaris2.10" --program-prefix="${__illumosArch}-illumos-" --with-sysroot="$__RootfsDir" --with-gnu-as \ + --with-gnu-ld --disable-nls --disable-libgomp --disable-libquadmath --disable-libssp --disable-libvtv --disable-libcilkrts --disable-libada --disable-libsanitizer \ + --disable-libquadmath-support --disable-shared --enable-tls + make -j "$JOBS" && make install && cd .. + BaseUrl=https://pkgsrc.smartos.org + if [[ "$__UseMirror" == 1 ]]; then + BaseUrl=https://pkgsrc.smartos.skylime.net + fi + BaseUrl="$BaseUrl/packages/SmartOS/trunk/${__illumosArch}/All" + echo "Downloading manifest" + wget "$BaseUrl" + echo "Downloading dependencies." + read -ra array <<<"$__IllumosPackages" + for package in "${array[@]}"; do + echo "Installing '$package'" + # find last occurrence of package in listing and extract its name + package="$(sed -En '/.*href="('"$package"'-[0-9].*).tgz".*/h;$!d;g;s//\1/p' All)" + echo "Resolved name '$package'" + wget "$BaseUrl"/"$package".tgz + ar -x "$package".tgz + tar --skip-old-files -xzf "$package".tmp.tg* -C "$__RootfsDir" 2>/dev/null + done + echo "Cleaning up temporary files." + popd + rm -rf "$__RootfsDir"/{tmp,+*} + mkdir -p "$__RootfsDir"/usr/include/net + mkdir -p "$__RootfsDir"/usr/include/netpacket + wget -P "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/bpf.h + wget -P "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/dlt.h + wget -P "$__RootfsDir"/usr/include/netpacket https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/inet/sockmods/netpacket/packet.h + wget -P "$__RootfsDir"/usr/include/sys https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/sys/sdt.h +elif [[ "$__CodeName" == "haiku" ]]; then + JOBS=${MAXJOBS:="$(getconf _NPROCESSORS_ONLN)"} + + echo "Building Haiku sysroot for $__HaikuArch" + mkdir -p "$__RootfsDir/tmp" + pushd "$__RootfsDir/tmp" + + mkdir "$__RootfsDir/tmp/download" + + echo "Downloading Haiku package tool" + git clone https://github.com/haiku/haiku-toolchains-ubuntu --depth 1 $__RootfsDir/tmp/script + wget -O "$__RootfsDir/tmp/download/hosttools.zip" $($__RootfsDir/tmp/script/fetch.sh --hosttools) + unzip -o "$__RootfsDir/tmp/download/hosttools.zip" -d "$__RootfsDir/tmp/bin" + + DepotBaseUrl="https://depot.haiku-os.org/__api/v2/pkg/get-pkg" + HpkgBaseUrl="https://eu.hpkg.haiku-os.org/haiku/master/$__HaikuArch/current" + + # Download Haiku packages + echo "Downloading Haiku packages" + read -ra array <<<"$__HaikuPackages" + for package in "${array[@]}"; do + echo "Downloading $package..." + # API documented here: https://github.com/haiku/haikudepotserver/blob/master/haikudepotserver-api2/src/main/resources/api2/pkg.yaml#L60 + # The schema here: https://github.com/haiku/haikudepotserver/blob/master/haikudepotserver-api2/src/main/resources/api2/pkg.yaml#L598 + hpkgDownloadUrl="$(wget -qO- --post-data='{"name":"'"$package"'","repositorySourceCode":"haikuports_'$__HaikuArch'","versionType":"LATEST","naturalLanguageCode":"en"}' \ + --header='Content-Type:application/json' "$DepotBaseUrl" | jq -r '.result.versions[].hpkgDownloadURL')" + wget -P "$__RootfsDir/tmp/download" "$hpkgDownloadUrl" + done + for package in haiku haiku_devel; do + echo "Downloading $package..." + hpkgVersion="$(wget -qO- $HpkgBaseUrl | sed -n 's/^.*version: "\([^"]*\)".*$/\1/p')" + wget -P "$__RootfsDir/tmp/download" "$HpkgBaseUrl/packages/$package-$hpkgVersion-1-$__HaikuArch.hpkg" + done + + # Set up the sysroot + echo "Setting up sysroot and extracting required packages" + mkdir -p "$__RootfsDir/boot/system" + for file in "$__RootfsDir/tmp/download/"*.hpkg; do + echo "Extracting $file..." + LD_LIBRARY_PATH="$__RootfsDir/tmp/bin" "$__RootfsDir/tmp/bin/package" extract -C "$__RootfsDir/boot/system" "$file" + done + + # Download buildtools + echo "Downloading Haiku buildtools" + wget -O "$__RootfsDir/tmp/download/buildtools.zip" $($__RootfsDir/tmp/script/fetch.sh --buildtools --arch=$__HaikuArch) + unzip -o "$__RootfsDir/tmp/download/buildtools.zip" -d "$__RootfsDir" + + # Cleaning up temporary files + echo "Cleaning up temporary files" + popd + rm -rf "$__RootfsDir/tmp" +elif [[ -n "$__CodeName" ]]; then + + if [[ "$__SkipSigCheck" == "0" ]]; then + __Keyring="$__Keyring --force-check-gpg" + fi + + debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo" + cp "$__CrossDir/$__BuildArch/sources.list.$__CodeName" "$__RootfsDir/etc/apt/sources.list" + chroot "$__RootfsDir" apt-get update + chroot "$__RootfsDir" apt-get -f -y install + chroot "$__RootfsDir" apt-get -y install $__UbuntuPackages + chroot "$__RootfsDir" symlinks -cr /usr + chroot "$__RootfsDir" apt-get clean + + if [[ "$__SkipUnmount" == "0" ]]; then + umount "$__RootfsDir"/* || true + fi + + if [[ "$__BuildArch" == "armel" && "$__CodeName" == "jessie" ]]; then + pushd "$__RootfsDir" + patch -p1 < "$__CrossDir/$__BuildArch/armel.jessie.patch" + popd + fi +elif [[ "$__Tizen" == "tizen" ]]; then + ROOTFS_DIR="$__RootfsDir" "$__CrossDir/tizen-build-rootfs.sh" "$__BuildArch" +else + echo "Unsupported target platform." + usage; + exit 1 +fi diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/ppc64le/sources.list.bionic dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/ppc64le/sources.list.bionic --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/ppc64le/sources.list.bionic 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/ppc64le/sources.list.bionic 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/riscv64/sources.list.sid dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/riscv64/sources.list.sid --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/riscv64/sources.list.sid 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/riscv64/sources.list.sid 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1 @@ +deb http://deb.debian.org/debian-ports sid main diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/s390x/sources.list.bionic dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/s390x/sources.list.bionic --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/s390x/sources.list.bionic 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/s390x/sources.list.bionic 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/tizen-build-rootfs.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/tizen-build-rootfs.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/tizen-build-rootfs.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/tizen-build-rootfs.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -e + +ARCH=$1 +LINK_ARCH=$ARCH + +case "$ARCH" in + arm) + TIZEN_ARCH="armv7hl" + ;; + armel) + TIZEN_ARCH="armv7l" + LINK_ARCH="arm" + ;; + arm64) + TIZEN_ARCH="aarch64" + ;; + x86) + TIZEN_ARCH="i686" + ;; + x64) + TIZEN_ARCH="x86_64" + LINK_ARCH="x86" + ;; + *) + echo "Unsupported architecture for tizen: $ARCH" + exit 1 +esac + +__CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +__TIZEN_CROSSDIR="$__CrossDir/${ARCH}/tizen" + +if [[ -z "$ROOTFS_DIR" ]]; then + echo "ROOTFS_DIR is not defined." + exit 1; +fi + +TIZEN_TMP_DIR=$ROOTFS_DIR/tizen_tmp +mkdir -p $TIZEN_TMP_DIR + +# Download files +echo ">>Start downloading files" +VERBOSE=1 $__CrossDir/tizen-fetch.sh $TIZEN_TMP_DIR $TIZEN_ARCH +echo "<>Start constructing Tizen rootfs" +TIZEN_RPM_FILES=`ls $TIZEN_TMP_DIR/*.rpm` +cd $ROOTFS_DIR +for f in $TIZEN_RPM_FILES; do + rpm2cpio $f | cpio -idm --quiet +done +echo "<>Start configuring Tizen rootfs" +ln -sfn asm-${LINK_ARCH} ./usr/include/asm +patch -p1 < $__TIZEN_CROSSDIR/tizen.patch +echo "</dev/null; then + VERBOSE=0 +fi + +Log() +{ + if [ $VERBOSE -ge $1 ]; then + echo ${@:2} + fi +} + +Inform() +{ + Log 1 -e "\x1B[0;34m$@\x1B[m" +} + +Debug() +{ + Log 2 -e "\x1B[0;32m$@\x1B[m" +} + +Error() +{ + >&2 Log 0 -e "\x1B[0;31m$@\x1B[m" +} + +Fetch() +{ + URL=$1 + FILE=$2 + PROGRESS=$3 + if [ $VERBOSE -ge 1 ] && [ $PROGRESS ]; then + CURL_OPT="--progress-bar" + else + CURL_OPT="--silent" + fi + curl $CURL_OPT $URL > $FILE +} + +hash curl 2> /dev/null || { Error "Require 'curl' Aborting."; exit 1; } +hash xmllint 2> /dev/null || { Error "Require 'xmllint' Aborting."; exit 1; } +hash sha256sum 2> /dev/null || { Error "Require 'sha256sum' Aborting."; exit 1; } + +TMPDIR=$1 +if [ ! -d $TMPDIR ]; then + TMPDIR=./tizen_tmp + Debug "Create temporary directory : $TMPDIR" + mkdir -p $TMPDIR +fi + +TIZEN_ARCH=$2 + +TIZEN_URL=http://download.tizen.org/snapshots/TIZEN/Tizen +BUILD_XML=build.xml +REPOMD_XML=repomd.xml +PRIMARY_XML=primary.xml +TARGET_URL="http://__not_initialized" + +Xpath_get() +{ + XPATH_RESULT='' + XPATH=$1 + XML_FILE=$2 + RESULT=$(xmllint --xpath $XPATH $XML_FILE) + if [[ -z ${RESULT// } ]]; then + Error "Can not find target from $XML_FILE" + Debug "Xpath = $XPATH" + exit 1 + fi + XPATH_RESULT=$RESULT +} + +fetch_tizen_pkgs_init() +{ + TARGET=$1 + PROFILE=$2 + Debug "Initialize TARGET=$TARGET, PROFILE=$PROFILE" + + TMP_PKG_DIR=$TMPDIR/tizen_${PROFILE}_pkgs + if [ -d $TMP_PKG_DIR ]; then rm -rf $TMP_PKG_DIR; fi + mkdir -p $TMP_PKG_DIR + + PKG_URL=$TIZEN_URL/$PROFILE/latest + + BUILD_XML_URL=$PKG_URL/$BUILD_XML + TMP_BUILD=$TMP_PKG_DIR/$BUILD_XML + TMP_REPOMD=$TMP_PKG_DIR/$REPOMD_XML + TMP_PRIMARY=$TMP_PKG_DIR/$PRIMARY_XML + TMP_PRIMARYGZ=${TMP_PRIMARY}.gz + + Fetch $BUILD_XML_URL $TMP_BUILD + + Debug "fetch $BUILD_XML_URL to $TMP_BUILD" + + TARGET_XPATH="//build/buildtargets/buildtarget[@name=\"$TARGET\"]/repo[@type=\"binary\"]/text()" + Xpath_get $TARGET_XPATH $TMP_BUILD + TARGET_PATH=$XPATH_RESULT + TARGET_URL=$PKG_URL/$TARGET_PATH + + REPOMD_URL=$TARGET_URL/repodata/repomd.xml + PRIMARY_XPATH='string(//*[local-name()="data"][@type="primary"]/*[local-name()="location"]/@href)' + + Fetch $REPOMD_URL $TMP_REPOMD + + Debug "fetch $REPOMD_URL to $TMP_REPOMD" + + Xpath_get $PRIMARY_XPATH $TMP_REPOMD + PRIMARY_XML_PATH=$XPATH_RESULT + PRIMARY_URL=$TARGET_URL/$PRIMARY_XML_PATH + + Fetch $PRIMARY_URL $TMP_PRIMARYGZ + + Debug "fetch $PRIMARY_URL to $TMP_PRIMARYGZ" + + gunzip $TMP_PRIMARYGZ + + Debug "unzip $TMP_PRIMARYGZ to $TMP_PRIMARY" +} + +fetch_tizen_pkgs() +{ + ARCH=$1 + PACKAGE_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="location"]/@href)' + + PACKAGE_CHECKSUM_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="checksum"]/text())' + + for pkg in ${@:2} + do + Inform "Fetching... $pkg" + XPATH=${PACKAGE_XPATH_TPL/_PKG_/$pkg} + XPATH=${XPATH/_ARCH_/$ARCH} + Xpath_get $XPATH $TMP_PRIMARY + PKG_PATH=$XPATH_RESULT + + XPATH=${PACKAGE_CHECKSUM_XPATH_TPL/_PKG_/$pkg} + XPATH=${XPATH/_ARCH_/$ARCH} + Xpath_get $XPATH $TMP_PRIMARY + CHECKSUM=$XPATH_RESULT + + PKG_URL=$TARGET_URL/$PKG_PATH + PKG_FILE=$(basename $PKG_PATH) + PKG_PATH=$TMPDIR/$PKG_FILE + + Debug "Download $PKG_URL to $PKG_PATH" + Fetch $PKG_URL $PKG_PATH true + + echo "$CHECKSUM $PKG_PATH" | sha256sum -c - > /dev/null + if [ $? -ne 0 ]; then + Error "Fail to fetch $PKG_URL to $PKG_PATH" + Debug "Checksum = $CHECKSUM" + exit 1 + fi + done +} + +Inform "Initialize ${TIZEN_ARCH} base" +fetch_tizen_pkgs_init standard Tizen-Base +Inform "fetch common packages" +fetch_tizen_pkgs ${TIZEN_ARCH} gcc gcc-devel-static glibc glibc-devel libicu libicu-devel libatomic linux-glibc-devel keyutils keyutils-devel libkeyutils +Inform "fetch coreclr packages" +fetch_tizen_pkgs ${TIZEN_ARCH} lldb lldb-devel libgcc libstdc++ libstdc++-devel libunwind libunwind-devel lttng-ust-devel lttng-ust userspace-rcu-devel userspace-rcu +Inform "fetch corefx packages" +fetch_tizen_pkgs ${TIZEN_ARCH} libcom_err libcom_err-devel zlib zlib-devel libopenssl11 libopenssl1.1-devel krb5 krb5-devel + +Inform "Initialize standard unified" +fetch_tizen_pkgs_init standard Tizen-Unified +Inform "fetch corefx packages" +fetch_tizen_pkgs ${TIZEN_ARCH} gssdp gssdp-devel tizen-release + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/toolchain.cmake dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/toolchain.cmake --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/toolchain.cmake 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/toolchain.cmake 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,377 @@ +set(CROSS_ROOTFS $ENV{ROOTFS_DIR}) + +# reset platform variables (e.g. cmake 3.25 sets LINUX=1) +unset(LINUX) +unset(FREEBSD) +unset(ILLUMOS) +unset(ANDROID) +unset(TIZEN) +unset(HAIKU) + +set(TARGET_ARCH_NAME $ENV{TARGET_BUILD_ARCH}) +if(EXISTS ${CROSS_ROOTFS}/bin/freebsd-version) + set(CMAKE_SYSTEM_NAME FreeBSD) + set(FREEBSD 1) +elseif(EXISTS ${CROSS_ROOTFS}/usr/platform/i86pc) + set(CMAKE_SYSTEM_NAME SunOS) + set(ILLUMOS 1) +elseif(EXISTS ${CROSS_ROOTFS}/boot/system/develop/headers/config/HaikuConfig.h) + set(CMAKE_SYSTEM_NAME Haiku) + set(HAIKU 1) +else() + set(CMAKE_SYSTEM_NAME Linux) + set(LINUX 1) +endif() +set(CMAKE_SYSTEM_VERSION 1) + +if(EXISTS ${CROSS_ROOTFS}/etc/tizen-release) + set(TIZEN 1) +elseif(EXISTS ${CROSS_ROOTFS}/android_platform) + set(ANDROID 1) +endif() + +if(TARGET_ARCH_NAME STREQUAL "arm") + set(CMAKE_SYSTEM_PROCESSOR armv7l) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv7-alpine-linux-musleabihf) + set(TOOLCHAIN "armv7-alpine-linux-musleabihf") + elseif(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv6-alpine-linux-musleabihf) + set(TOOLCHAIN "armv6-alpine-linux-musleabihf") + else() + set(TOOLCHAIN "arm-linux-gnueabihf") + endif() + if(TIZEN) + set(TIZEN_TOOLCHAIN "armv7hl-tizen-linux-gnueabihf/9.2.0") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "arm64") + set(CMAKE_SYSTEM_PROCESSOR aarch64) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/aarch64-alpine-linux-musl) + set(TOOLCHAIN "aarch64-alpine-linux-musl") + elseif(LINUX) + set(TOOLCHAIN "aarch64-linux-gnu") + if(TIZEN) + set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu/9.2.0") + endif() + elseif(FREEBSD) + set(triple "aarch64-unknown-freebsd12") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "armel") + set(CMAKE_SYSTEM_PROCESSOR armv7l) + set(TOOLCHAIN "arm-linux-gnueabi") + if(TIZEN) + set(TIZEN_TOOLCHAIN "armv7l-tizen-linux-gnueabi/9.2.0") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "armv6") + set(CMAKE_SYSTEM_PROCESSOR armv6l) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv6-alpine-linux-musleabihf) + set(TOOLCHAIN "armv6-alpine-linux-musleabihf") + else() + set(TOOLCHAIN "arm-linux-gnueabihf") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "ppc64le") + set(CMAKE_SYSTEM_PROCESSOR ppc64le) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/powerpc64le-alpine-linux-musl) + set(TOOLCHAIN "powerpc64le-alpine-linux-musl") + else() + set(TOOLCHAIN "powerpc64le-linux-gnu") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "riscv64") + set(CMAKE_SYSTEM_PROCESSOR riscv64) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/riscv64-alpine-linux-musl) + set(TOOLCHAIN "riscv64-alpine-linux-musl") + else() + set(TOOLCHAIN "riscv64-linux-gnu") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "s390x") + set(CMAKE_SYSTEM_PROCESSOR s390x) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/s390x-alpine-linux-musl) + set(TOOLCHAIN "s390x-alpine-linux-musl") + else() + set(TOOLCHAIN "s390x-linux-gnu") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "x64") + set(CMAKE_SYSTEM_PROCESSOR x86_64) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/x86_64-alpine-linux-musl) + set(TOOLCHAIN "x86_64-alpine-linux-musl") + elseif(LINUX) + set(TOOLCHAIN "x86_64-linux-gnu") + if(TIZEN) + set(TIZEN_TOOLCHAIN "x86_64-tizen-linux-gnu/9.2.0") + endif() + elseif(FREEBSD) + set(triple "x86_64-unknown-freebsd12") + elseif(ILLUMOS) + set(TOOLCHAIN "x86_64-illumos") + elseif(HAIKU) + set(TOOLCHAIN "x86_64-unknown-haiku") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "x86") + set(CMAKE_SYSTEM_PROCESSOR i686) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/i586-alpine-linux-musl) + set(TOOLCHAIN "i586-alpine-linux-musl") + else() + set(TOOLCHAIN "i686-linux-gnu") + endif() + if(TIZEN) + set(TIZEN_TOOLCHAIN "i586-tizen-linux-gnu/9.2.0") + endif() +else() + message(FATAL_ERROR "Arch is ${TARGET_ARCH_NAME}. Only arm, arm64, armel, armv6, ppc64le, riscv64, s390x, x64 and x86 are supported!") +endif() + +if(DEFINED ENV{TOOLCHAIN}) + set(TOOLCHAIN $ENV{TOOLCHAIN}) +endif() + +# Specify include paths +if(TIZEN) + if(TARGET_ARCH_NAME STREQUAL "arm") + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/) + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/armv7hl-tizen-linux-gnueabihf) + endif() + if(TARGET_ARCH_NAME STREQUAL "armel") + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/) + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/armv7l-tizen-linux-gnueabi) + endif() + if(TARGET_ARCH_NAME STREQUAL "arm64") + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/) + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/aarch64-tizen-linux-gnu) + endif() + if(TARGET_ARCH_NAME STREQUAL "x86") + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/) + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/i586-tizen-linux-gnu) + endif() + if(TARGET_ARCH_NAME STREQUAL "x64") + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/) + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/x86_64-tizen-linux-gnu) + endif() +endif() + +if(ANDROID) + if(TARGET_ARCH_NAME STREQUAL "arm") + set(ANDROID_ABI armeabi-v7a) + elseif(TARGET_ARCH_NAME STREQUAL "arm64") + set(ANDROID_ABI arm64-v8a) + endif() + + # extract platform number required by the NDK's toolchain + file(READ "${CROSS_ROOTFS}/android_platform" RID_FILE_CONTENTS) + string(REPLACE "RID=" "" ANDROID_RID "${RID_FILE_CONTENTS}") + string(REGEX REPLACE ".*\\.([0-9]+)-.*" "\\1" ANDROID_PLATFORM "${ANDROID_RID}") + + set(ANDROID_TOOLCHAIN clang) + set(FEATURE_EVENT_TRACE 0) # disable event trace as there is no lttng-ust package in termux repository + set(CMAKE_SYSTEM_LIBRARY_PATH "${CROSS_ROOTFS}/usr/lib") + set(CMAKE_SYSTEM_INCLUDE_PATH "${CROSS_ROOTFS}/usr/include") + + # include official NDK toolchain script + include(${CROSS_ROOTFS}/../build/cmake/android.toolchain.cmake) +elseif(FREEBSD) + # we cross-compile by instructing clang + set(CMAKE_C_COMPILER_TARGET ${triple}) + set(CMAKE_CXX_COMPILER_TARGET ${triple}) + set(CMAKE_ASM_COMPILER_TARGET ${triple}) + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fuse-ld=lld") +elseif(ILLUMOS) + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") + + include_directories(SYSTEM ${CROSS_ROOTFS}/include) + + set(TOOLSET_PREFIX ${TOOLCHAIN}-) + function(locate_toolchain_exec exec var) + string(TOUPPER ${exec} EXEC_UPPERCASE) + if(NOT "$ENV{CLR_${EXEC_UPPERCASE}}" STREQUAL "") + set(${var} "$ENV{CLR_${EXEC_UPPERCASE}}" PARENT_SCOPE) + return() + endif() + + find_program(EXEC_LOCATION_${exec} + NAMES + "${TOOLSET_PREFIX}${exec}${CLR_CMAKE_COMPILER_FILE_NAME_VERSION}" + "${TOOLSET_PREFIX}${exec}") + + if (EXEC_LOCATION_${exec} STREQUAL "EXEC_LOCATION_${exec}-NOTFOUND") + message(FATAL_ERROR "Unable to find toolchain executable. Name: ${exec}, Prefix: ${TOOLSET_PREFIX}.") + endif() + set(${var} ${EXEC_LOCATION_${exec}} PARENT_SCOPE) + endfunction() + + set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") + + locate_toolchain_exec(gcc CMAKE_C_COMPILER) + locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) + + set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") + set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") +elseif(HAIKU) + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") + set(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};${CROSS_ROOTFS}/cross-tools-x86_64/bin") + + set(TOOLSET_PREFIX ${TOOLCHAIN}-) + function(locate_toolchain_exec exec var) + string(TOUPPER ${exec} EXEC_UPPERCASE) + if(NOT "$ENV{CLR_${EXEC_UPPERCASE}}" STREQUAL "") + set(${var} "$ENV{CLR_${EXEC_UPPERCASE}}" PARENT_SCOPE) + return() + endif() + + find_program(EXEC_LOCATION_${exec} + NAMES + "${TOOLSET_PREFIX}${exec}${CLR_CMAKE_COMPILER_FILE_NAME_VERSION}" + "${TOOLSET_PREFIX}${exec}") + + if (EXEC_LOCATION_${exec} STREQUAL "EXEC_LOCATION_${exec}-NOTFOUND") + message(FATAL_ERROR "Unable to find toolchain executable. Name: ${exec}, Prefix: ${TOOLSET_PREFIX}.") + endif() + set(${var} ${EXEC_LOCATION_${exec}} PARENT_SCOPE) + endfunction() + + set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") + + locate_toolchain_exec(gcc CMAKE_C_COMPILER) + locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) + + set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") + set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") + + # let CMake set up the correct search paths + include(Platform/Haiku) +else() + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") + + set(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") + set(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") + set(CMAKE_ASM_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") +endif() + +# Specify link flags + +function(add_toolchain_linker_flag Flag) + set(Config "${ARGV1}") + set(CONFIG_SUFFIX "") + if (NOT Config STREQUAL "") + set(CONFIG_SUFFIX "_${Config}") + endif() + set("CMAKE_EXE_LINKER_FLAGS${CONFIG_SUFFIX}_INIT" "${CMAKE_EXE_LINKER_FLAGS${CONFIG_SUFFIX}_INIT} ${Flag}" PARENT_SCOPE) + set("CMAKE_SHARED_LINKER_FLAGS${CONFIG_SUFFIX}_INIT" "${CMAKE_SHARED_LINKER_FLAGS${CONFIG_SUFFIX}_INIT} ${Flag}" PARENT_SCOPE) +endfunction() + +if(LINUX) + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib/${TOOLCHAIN}") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib/${TOOLCHAIN}") +endif() + +if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$") + if(TIZEN) + add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + endif() +elseif(TARGET_ARCH_NAME MATCHES "^(arm64|x64)$") + if(TIZEN) + add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib64") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib64") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "x86") + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/i586-alpine-linux-musl) + add_toolchain_linker_flag("--target=${TOOLCHAIN}") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib/gcc/${TOOLCHAIN}") + endif() + add_toolchain_linker_flag(-m32) + if(TIZEN) + add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + endif() +elseif(ILLUMOS) + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib/amd64") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/amd64/lib") +elseif(HAIKU) + add_toolchain_linker_flag("-lnetwork") + add_toolchain_linker_flag("-lroot") +endif() + +# Specify compile options + +if((TARGET_ARCH_NAME MATCHES "^(arm|arm64|armel|armv6|ppc64le|riscv64|s390x|x64|x86)$" AND NOT ANDROID AND NOT FREEBSD) OR ILLUMOS OR HAIKU) + set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN}) + set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN}) + set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN}) +endif() + +if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$") + add_compile_options(-mthumb) + if (NOT DEFINED CLR_ARM_FPU_TYPE) + set (CLR_ARM_FPU_TYPE vfpv3) + endif (NOT DEFINED CLR_ARM_FPU_TYPE) + + add_compile_options (-mfpu=${CLR_ARM_FPU_TYPE}) + if (NOT DEFINED CLR_ARM_FPU_CAPABILITY) + set (CLR_ARM_FPU_CAPABILITY 0x7) + endif (NOT DEFINED CLR_ARM_FPU_CAPABILITY) + + add_definitions (-DCLR_ARM_FPU_CAPABILITY=${CLR_ARM_FPU_CAPABILITY}) + + # persist variables across multiple try_compile passes + list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES CLR_ARM_FPU_TYPE CLR_ARM_FPU_CAPABILITY) + + if(TARGET_ARCH_NAME STREQUAL "armel") + add_compile_options(-mfloat-abi=softfp) + endif() +elseif(TARGET_ARCH_NAME STREQUAL "x86") + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/i586-alpine-linux-musl) + add_compile_options(--target=${TOOLCHAIN}) + endif() + add_compile_options(-m32) + add_compile_options(-Wno-error=unused-command-line-argument) +endif() + +if(TIZEN) + if(TARGET_ARCH_NAME MATCHES "^(arm|armel|arm64|x86)$") + add_compile_options(-Wno-deprecated-declarations) # compile-time option + add_compile_options(-D__extern_always_inline=inline) # compile-time option + endif() +endif() + +# Set LLDB include and library paths for builds that need lldb. +if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") + if(TARGET_ARCH_NAME STREQUAL "x86") + set(LLVM_CROSS_DIR "$ENV{LLVM_CROSS_HOME}") + else() # arm/armel case + set(LLVM_CROSS_DIR "$ENV{LLVM_ARM_HOME}") + endif() + if(LLVM_CROSS_DIR) + set(WITH_LLDB_LIBS "${LLVM_CROSS_DIR}/lib/" CACHE STRING "") + set(WITH_LLDB_INCLUDES "${LLVM_CROSS_DIR}/include" CACHE STRING "") + set(LLDB_H "${WITH_LLDB_INCLUDES}" CACHE STRING "") + set(LLDB "${LLVM_CROSS_DIR}/lib/liblldb.so" CACHE STRING "") + else() + if(TARGET_ARCH_NAME STREQUAL "x86") + set(WITH_LLDB_LIBS "${CROSS_ROOTFS}/usr/lib/i386-linux-gnu" CACHE STRING "") + set(CHECK_LLVM_DIR "${CROSS_ROOTFS}/usr/lib/llvm-3.8/include") + if(EXISTS "${CHECK_LLVM_DIR}" AND IS_DIRECTORY "${CHECK_LLVM_DIR}") + set(WITH_LLDB_INCLUDES "${CHECK_LLVM_DIR}") + else() + set(WITH_LLDB_INCLUDES "${CROSS_ROOTFS}/usr/lib/llvm-3.6/include") + endif() + else() # arm/armel case + set(WITH_LLDB_LIBS "${CROSS_ROOTFS}/usr/lib/${TOOLCHAIN}" CACHE STRING "") + set(WITH_LLDB_INCLUDES "${CROSS_ROOTFS}/usr/lib/llvm-3.6/include" CACHE STRING "") + endif() + endif() +endif() + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x64/sources.list.bionic dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x64/sources.list.bionic --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x64/sources.list.bionic 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x64/sources.list.bionic 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ bionic main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ bionic main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ bionic-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ bionic-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x64/sources.list.xenial dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x64/sources.list.xenial --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x64/sources.list.xenial 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x64/sources.list.xenial 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ xenial main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ xenial main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ xenial-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ xenial-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x64/tizen/tizen.patch dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x64/tizen/tizen.patch --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x64/tizen/tizen.patch 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x64/tizen/tizen.patch 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +diff -u -r a/usr/lib64/libc.so b/usr/lib64/libc.so +--- a/usr/lib64/libc.so 2016-12-30 23:00:08.284951863 +0900 ++++ b/usr/lib64/libc.so 2016-12-30 23:00:32.140951815 +0900 +@@ -2,4 +2,4 @@ + Use the shared library, but some functions are only in + the static library, so try that secondarily. */ + OUTPUT_FORMAT(elf64-x86-64) +-GROUP ( /lib64/libc.so.6 /usr/lib64/libc_nonshared.a AS_NEEDED ( /lib64/ld-linux-x86-64.so.2 ) ) ++GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux-x86-64.so.2 ) ) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x86/sources.list.bionic dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x86/sources.list.bionic --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x86/sources.list.bionic 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x86/sources.list.bionic 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ bionic main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ bionic main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ bionic-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ bionic-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x86/sources.list.focal dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x86/sources.list.focal --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x86/sources.list.focal 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x86/sources.list.focal 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ focal main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ focal-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x86/sources.list.jammy dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x86/sources.list.jammy --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x86/sources.list.jammy 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x86/sources.list.jammy 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ jammy main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ jammy main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x86/sources.list.xenial dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x86/sources.list.xenial --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x86/sources.list.xenial 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x86/sources.list.xenial 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ xenial main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ xenial main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ xenial-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ xenial-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x86/tizen/tizen.patch dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x86/tizen/tizen.patch --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/cross/x86/tizen/tizen.patch 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/cross/x86/tizen/tizen.patch 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so +--- a/usr/lib/libc.so 2016-12-30 23:00:08.284951863 +0900 ++++ b/usr/lib/libc.so 2016-12-30 23:00:32.140951815 +0900 +@@ -2,4 +2,4 @@ + Use the shared library, but some functions are only in + the static library, so try that secondarily. */ + OUTPUT_FORMAT(elf32-i386) +-GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /lib/ld-linux.so.2 ) ) ++GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux.so.2 ) ) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/darc-init.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/darc-init.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/darc-init.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/darc-init.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,47 @@ +param ( + $darcVersion = $null, + $versionEndpoint = 'https://maestro-prod.westus2.cloudapp.azure.com/api/assets/darc-version?api-version=2019-01-16', + $verbosity = 'minimal', + $toolpath = $null +) + +. $PSScriptRoot\tools.ps1 + +function InstallDarcCli ($darcVersion, $toolpath) { + $darcCliPackageName = 'microsoft.dotnet.darc' + + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + $toolList = & "$dotnet" tool list -g + + if ($toolList -like "*$darcCliPackageName*") { + & "$dotnet" tool uninstall $darcCliPackageName -g + } + + # If the user didn't explicitly specify the darc version, + # query the Maestro API for the correct version of darc to install. + if (-not $darcVersion) { + $darcVersion = $(Invoke-WebRequest -Uri $versionEndpoint -UseBasicParsing).Content + } + + $arcadeServicesSource = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + + Write-Host "Installing Darc CLI version $darcVersion..." + Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' + if (-not $toolpath) { + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity -g" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g + }else { + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity --tool-path '$toolpath'" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath" + } +} + +try { + InstallDarcCli $darcVersion $toolpath +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Darc' -Message $_ + ExitWithExitCode 1 +} \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/darc-init.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/darc-init.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/darc-init.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/darc-init.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +darcVersion='' +versionEndpoint='https://maestro-prod.westus2.cloudapp.azure.com/api/assets/darc-version?api-version=2019-01-16' +verbosity='minimal' + +while [[ $# > 0 ]]; do + opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + --darcversion) + darcVersion=$2 + shift + ;; + --versionendpoint) + versionEndpoint=$2 + shift + ;; + --verbosity) + verbosity=$2 + shift + ;; + --toolpath) + toolpath=$2 + shift + ;; + *) + echo "Invalid argument: $1" + usage + exit 1 + ;; + esac + + shift +done + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/tools.sh" + +if [ -z "$darcVersion" ]; then + darcVersion=$(curl -X GET "$versionEndpoint" -H "accept: text/plain") +fi + +function InstallDarcCli { + local darc_cli_package_name="microsoft.dotnet.darc" + + InitializeDotNetCli true + local dotnet_root=$_InitializeDotNetCli + + if [ -z "$toolpath" ]; then + local tool_list=$($dotnet_root/dotnet tool list -g) + if [[ $tool_list = *$darc_cli_package_name* ]]; then + echo $($dotnet_root/dotnet tool uninstall $darc_cli_package_name -g) + fi + else + local tool_list=$($dotnet_root/dotnet tool list --tool-path "$toolpath") + if [[ $tool_list = *$darc_cli_package_name* ]]; then + echo $($dotnet_root/dotnet tool uninstall $darc_cli_package_name --tool-path "$toolpath") + fi + fi + + local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" + + echo "Installing Darc CLI version $darcVersion..." + echo "You may need to restart your command shell if this is the first dotnet tool you have installed." + if [ -z "$toolpath" ]; then + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g) + else + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath") + fi +} + +InstallDarcCli diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/dotnet-install.cmd dotnet8-8.0.100-8.0.0/src/aspire/eng/common/dotnet-install.cmd --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/dotnet-install.cmd 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/dotnet-install.cmd 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,2 @@ +@echo off +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0dotnet-install.ps1""" %*" \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/dotnet-install.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/dotnet-install.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/dotnet-install.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/dotnet-install.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,28 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string] $verbosity = 'minimal', + [string] $architecture = '', + [string] $version = 'Latest', + [string] $runtime = 'dotnet', + [string] $RuntimeSourceFeed = '', + [string] $RuntimeSourceFeedKey = '' +) + +. $PSScriptRoot\tools.ps1 + +$dotnetRoot = Join-Path $RepoRoot '.dotnet' + +$installdir = $dotnetRoot +try { + if ($architecture -and $architecture.Trim() -eq 'x86') { + $installdir = Join-Path $installdir 'x86' + } + InstallDotNet $installdir $version $architecture $runtime $true -RuntimeSourceFeed $RuntimeSourceFeed -RuntimeSourceFeedKey $RuntimeSourceFeedKey +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ + ExitWithExitCode 1 +} + +ExitWithExitCode 0 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/dotnet-install.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/dotnet-install.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/dotnet-install.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/dotnet-install.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/tools.sh" + +version='Latest' +architecture='' +runtime='dotnet' +runtimeSourceFeed='' +runtimeSourceFeedKey='' +while [[ $# > 0 ]]; do + opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -version|-v) + shift + version="$1" + ;; + -architecture|-a) + shift + architecture="$1" + ;; + -runtime|-r) + shift + runtime="$1" + ;; + -runtimesourcefeed) + shift + runtimeSourceFeed="$1" + ;; + -runtimesourcefeedkey) + shift + runtimeSourceFeedKey="$1" + ;; + *) + Write-PipelineTelemetryError -Category 'Build' -Message "Invalid argument: $1" + exit 1 + ;; + esac + shift +done + +# Use uname to determine what the CPU is, see https://en.wikipedia.org/wiki/Uname#Examples +cpuname=$(uname -m) +case $cpuname in + arm64|aarch64) + buildarch=arm64 + if [ "$(getconf LONG_BIT)" -lt 64 ]; then + # This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS) + buildarch=arm + fi + ;; + loongarch64) + buildarch=loongarch64 + ;; + amd64|x86_64) + buildarch=x64 + ;; + armv*l) + buildarch=arm + ;; + i[3-6]86) + buildarch=x86 + ;; + *) + echo "Unknown CPU $cpuname detected, treating it as x64" + buildarch=x64 + ;; +esac + +dotnetRoot="${repo_root}.dotnet" +if [[ $architecture != "" ]] && [[ $architecture != $buildarch ]]; then + dotnetRoot="$dotnetRoot/$architecture" +fi + +InstallDotNet $dotnetRoot $version "$architecture" $runtime true $runtimeSourceFeed $runtimeSourceFeedKey || { + local exit_code=$? + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "dotnet-install.sh failed (exit code '$exit_code')." >&2 + ExitWithExitCode $exit_code +} + +ExitWithExitCode 0 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/enable-cross-org-publishing.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/enable-cross-org-publishing.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/enable-cross-org-publishing.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/enable-cross-org-publishing.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,13 @@ +param( + [string] $token +) + + +. $PSScriptRoot\pipeline-logging-functions.ps1 + +# Write-PipelineSetVariable will no-op if a variable named $ci is not defined +# Since this script is only ever called in AzDO builds, just universally set it +$ci = $true + +Write-PipelineSetVariable -Name 'VSS_NUGET_ACCESSTOKEN' -Value $token -IsMultiJobVariable $false +Write-PipelineSetVariable -Name 'VSS_NUGET_URI_PREFIXES' -Value 'https://dnceng.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/dnceng/;https://devdiv.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/devdiv/' -IsMultiJobVariable $false diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/generate-locproject.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/generate-locproject.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/generate-locproject.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/generate-locproject.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,189 @@ +Param( + [Parameter(Mandatory=$true)][string] $SourcesDirectory, # Directory where source files live; if using a Localize directory it should live in here + [string] $LanguageSet = 'VS_Main_Languages', # Language set to be used in the LocProject.json + [switch] $UseCheckedInLocProjectJson, # When set, generates a LocProject.json and compares it to one that already exists in the repo; otherwise just generates one + [switch] $CreateNeutralXlfs # Creates neutral xlf files. Only set to false when running locally +) + +# Generates LocProject.json files for the OneLocBuild task. OneLocBuildTask is described here: +# https://ceapex.visualstudio.com/CEINTL/_wiki/wikis/CEINTL.wiki/107/Localization-with-OneLocBuild-Task + +Set-StrictMode -Version 2.0 +$ErrorActionPreference = "Stop" +. $PSScriptRoot\pipeline-logging-functions.ps1 + +$exclusionsFilePath = "$SourcesDirectory\eng\Localize\LocExclusions.json" +$exclusions = @{ Exclusions = @() } +if (Test-Path -Path $exclusionsFilePath) +{ + $exclusions = Get-Content "$exclusionsFilePath" | ConvertFrom-Json +} + +Push-Location "$SourcesDirectory" # push location for Resolve-Path -Relative to work + +# Template files +$jsonFiles = @() +$jsonTemplateFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\.template\.config\\localize\\.+\.en\.json" } # .NET templating pattern +$jsonTemplateFiles | ForEach-Object { + $null = $_.Name -Match "(.+)\.[\w-]+\.json" # matches '[filename].[langcode].json + + $destinationFile = "$($_.Directory.FullName)\$($Matches.1).json" + $jsonFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru +} + +$jsonWinformsTemplateFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\\strings\.json" } # current winforms pattern + +$wxlFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\\.+\.wxl" -And -Not( $_.Directory.Name -Match "\d{4}" ) } # localized files live in four digit lang ID directories; this excludes them +if (-not $wxlFiles) { + $wxlEnFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\\1033\\.+\.wxl" } # pick up en files (1033 = en) specifically so we can copy them to use as the neutral xlf files + if ($wxlEnFiles) { + $wxlFiles = @() + $wxlEnFiles | ForEach-Object { + $destinationFile = "$($_.Directory.Parent.FullName)\$($_.Name)" + $wxlFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru + } + } +} + +$macosHtmlEnFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\.lproj\\.+\.html$" } # add installer HTML files +$macosHtmlFiles = @() +if ($macosHtmlEnFiles) { + $macosHtmlEnFiles | ForEach-Object { + $destinationFile = "$($_.Directory.Parent.FullName)\$($_.Name)" + $macosHtmlFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru + } +} + +$xlfFiles = @() + +$allXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.xlf" +$langXlfFiles = @() +if ($allXlfFiles) { + $null = $allXlfFiles[0].FullName -Match "\.([\w-]+)\.xlf" # matches '[langcode].xlf' + $firstLangCode = $Matches.1 + $langXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.$firstLangCode.xlf" +} +$langXlfFiles | ForEach-Object { + $null = $_.Name -Match "(.+)\.[\w-]+\.xlf" # matches '[filename].[langcode].xlf + + $destinationFile = "$($_.Directory.FullName)\$($Matches.1).xlf" + $xlfFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru +} + +$locFiles = $jsonFiles + $jsonWinformsTemplateFiles + $xlfFiles + +$locJson = @{ + Projects = @( + @{ + LanguageSet = $LanguageSet + LocItems = @( + $locFiles | ForEach-Object { + $outputPath = "$(($_.DirectoryName | Resolve-Path -Relative) + "\")" + $continue = $true + foreach ($exclusion in $exclusions.Exclusions) { + if ($_.FullName.Contains($exclusion)) + { + $continue = $false + } + } + $sourceFile = ($_.FullName | Resolve-Path -Relative) + if (!$CreateNeutralXlfs -and $_.Extension -eq '.xlf') { + Remove-Item -Path $sourceFile + } + if ($continue) + { + if ($_.Directory.Name -eq 'en' -and $_.Extension -eq '.json') { + return @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnPath" + OutputPath = "$($_.Directory.Parent.FullName | Resolve-Path -Relative)\" + } + } else { + return @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnName" + OutputPath = $outputPath + } + } + } + } + ) + }, + @{ + LanguageSet = $LanguageSet + CloneLanguageSet = "WiX_CloneLanguages" + LssFiles = @( "wxl_loc.lss" ) + LocItems = @( + $wxlFiles | ForEach-Object { + $outputPath = "$($_.Directory.FullName | Resolve-Path -Relative)\" + $continue = $true + foreach ($exclusion in $exclusions.Exclusions) { + if ($_.FullName.Contains($exclusion)) { + $continue = $false + } + } + $sourceFile = ($_.FullName | Resolve-Path -Relative) + if ($continue) + { + return @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnPath" + OutputPath = $outputPath + } + } + } + ) + }, + @{ + LanguageSet = $LanguageSet + CloneLanguageSet = "VS_macOS_CloneLanguages" + LssFiles = @( ".\eng\common\loc\P22DotNetHtmlLocalization.lss" ) + LocItems = @( + $macosHtmlFiles | ForEach-Object { + $outputPath = "$($_.Directory.FullName | Resolve-Path -Relative)\" + $continue = $true + foreach ($exclusion in $exclusions.Exclusions) { + if ($_.FullName.Contains($exclusion)) { + $continue = $false + } + } + $sourceFile = ($_.FullName | Resolve-Path -Relative) + $lciFile = $sourceFile + ".lci" + if ($continue) { + $result = @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnPath" + OutputPath = $outputPath + } + if (Test-Path $lciFile -PathType Leaf) { + $result["LciFile"] = $lciFile + } + return $result + } + } + ) + } + ) +} + +$json = ConvertTo-Json $locJson -Depth 5 +Write-Host "LocProject.json generated:`n`n$json`n`n" +Pop-Location + +if (!$UseCheckedInLocProjectJson) { + New-Item "$SourcesDirectory\eng\Localize\LocProject.json" -Force # Need this to make sure the Localize directory is created + Set-Content "$SourcesDirectory\eng\Localize\LocProject.json" $json +} +else { + New-Item "$SourcesDirectory\eng\Localize\LocProject-generated.json" -Force # Need this to make sure the Localize directory is created + Set-Content "$SourcesDirectory\eng\Localize\LocProject-generated.json" $json + + if ((Get-FileHash "$SourcesDirectory\eng\Localize\LocProject-generated.json").Hash -ne (Get-FileHash "$SourcesDirectory\eng\Localize\LocProject.json").Hash) { + Write-PipelineTelemetryError -Category "OneLocBuild" -Message "Existing LocProject.json differs from generated LocProject.json. Download LocProject-generated.json and compare them." + + exit 1 + } + else { + Write-Host "Generated LocProject.json and current LocProject.json are identical." + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/generate-sbom-prep.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/generate-sbom-prep.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/generate-sbom-prep.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/generate-sbom-prep.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,21 @@ +Param( + [Parameter(Mandatory=$true)][string] $ManifestDirPath # Manifest directory where sbom will be placed +) + +. $PSScriptRoot\pipeline-logging-functions.ps1 + +Write-Host "Creating dir $ManifestDirPath" +# create directory for sbom manifest to be placed +if (!(Test-Path -path $ManifestDirPath)) +{ + New-Item -ItemType Directory -path $ManifestDirPath + Write-Host "Successfully created directory $ManifestDirPath" +} +else{ + Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." +} + +Write-Host "Updating artifact name" +$artifact_name = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM" -replace '["/:<>\\|?@*"() ]', '_' +Write-Host "Artifact name $artifact_name" +Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$artifact_name" diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/generate-sbom-prep.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/generate-sbom-prep.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/generate-sbom-prep.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/generate-sbom-prep.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" +. $scriptroot/pipeline-logging-functions.sh + +manifest_dir=$1 + +if [ ! -d "$manifest_dir" ] ; then + mkdir -p "$manifest_dir" + echo "Sbom directory created." $manifest_dir +else + Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." +fi + +artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM" +echo "Artifact name before : "$artifact_name +# replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts. +safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}" +echo "Artifact name after : "$safe_artifact_name +export ARTIFACT_NAME=$safe_artifact_name +echo "##vso[task.setvariable variable=ARTIFACT_NAME]$safe_artifact_name" + +exit 0 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/helixpublish.proj dotnet8-8.0.100-8.0.0/src/aspire/eng/common/helixpublish.proj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/helixpublish.proj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/helixpublish.proj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,26 @@ + + + + msbuild + + + + + %(Identity) + + + + + + $(WorkItemDirectory) + $(WorkItemCommand) + $(WorkItemTimeout) + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/init-tools-native.cmd dotnet8-8.0.100-8.0.0/src/aspire/eng/common/init-tools-native.cmd --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/init-tools-native.cmd 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/init-tools-native.cmd 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,3 @@ +@echo off +powershell -NoProfile -NoLogo -ExecutionPolicy ByPass -command "& """%~dp0init-tools-native.ps1""" %*" +exit /b %ErrorLevel% \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/init-tools-native.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/init-tools-native.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/init-tools-native.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/init-tools-native.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,203 @@ +<# +.SYNOPSIS +Entry point script for installing native tools + +.DESCRIPTION +Reads $RepoRoot\global.json file to determine native assets to install +and executes installers for those tools + +.PARAMETER BaseUri +Base file directory or Url from which to acquire tool archives + +.PARAMETER InstallDirectory +Directory to install native toolset. This is a command-line override for the default +Install directory precedence order: +- InstallDirectory command-line override +- NETCOREENG_INSTALL_DIRECTORY environment variable +- (default) %USERPROFILE%/.netcoreeng/native + +.PARAMETER Clean +Switch specifying to not install anything, but cleanup native asset folders + +.PARAMETER Force +Clean and then install tools + +.PARAMETER DownloadRetries +Total number of retry attempts + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds + +.PARAMETER GlobalJsonFile +File path to global.json file + +.PARAMETER PathPromotion +Optional switch to enable either promote native tools specified in the global.json to the path (in Azure Pipelines) +or break the build if a native tool is not found on the path (on a local dev machine) + +.NOTES +#> +[CmdletBinding(PositionalBinding=$false)] +Param ( + [string] $BaseUri = 'https://netcorenativeassets.blob.core.windows.net/resource-packages/external', + [string] $InstallDirectory, + [switch] $Clean = $False, + [switch] $Force = $False, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30, + [string] $GlobalJsonFile, + [switch] $PathPromotion +) + +if (!$GlobalJsonFile) { + $GlobalJsonFile = Join-Path (Get-Item $PSScriptRoot).Parent.Parent.FullName 'global.json' +} + +Set-StrictMode -version 2.0 +$ErrorActionPreference='Stop' + +. $PSScriptRoot\pipeline-logging-functions.ps1 +Import-Module -Name (Join-Path $PSScriptRoot 'native\CommonLibrary.psm1') + +try { + # Define verbose switch if undefined + $Verbose = $VerbosePreference -Eq 'Continue' + + $EngCommonBaseDir = Join-Path $PSScriptRoot 'native\' + $NativeBaseDir = $InstallDirectory + if (!$NativeBaseDir) { + $NativeBaseDir = CommonLibrary\Get-NativeInstallDirectory + } + $Env:CommonLibrary_NativeInstallDir = $NativeBaseDir + $InstallBin = Join-Path $NativeBaseDir 'bin' + $InstallerPath = Join-Path $EngCommonBaseDir 'install-tool.ps1' + + # Process tools list + Write-Host "Processing $GlobalJsonFile" + If (-Not (Test-Path $GlobalJsonFile)) { + Write-Host "Unable to find '$GlobalJsonFile'" + exit 0 + } + $NativeTools = Get-Content($GlobalJsonFile) -Raw | + ConvertFrom-Json | + Select-Object -Expand 'native-tools' -ErrorAction SilentlyContinue + if ($NativeTools) { + if ($PathPromotion -eq $True) { + $ArcadeToolsDirectory = "$env:SYSTEMDRIVE\arcade-tools" + if (Test-Path $ArcadeToolsDirectory) { # if this directory exists, we should use native tools on machine + $NativeTools.PSObject.Properties | ForEach-Object { + $ToolName = $_.Name + $ToolVersion = $_.Value + $InstalledTools = @{} + + if ((Get-Command "$ToolName" -ErrorAction SilentlyContinue) -eq $null) { + if ($ToolVersion -eq "latest") { + $ToolVersion = "" + } + $ToolDirectories = (Get-ChildItem -Path "$ArcadeToolsDirectory" -Filter "$ToolName-$ToolVersion*" | Sort-Object -Descending) + if ($ToolDirectories -eq $null) { + Write-Error "Unable to find directory for $ToolName $ToolVersion; please make sure the tool is installed on this image." + exit 1 + } + $ToolDirectory = $ToolDirectories[0] + $BinPathFile = "$($ToolDirectory.FullName)\binpath.txt" + if (-not (Test-Path -Path "$BinPathFile")) { + Write-Error "Unable to find binpath.txt in '$($ToolDirectory.FullName)' ($ToolName $ToolVersion); artifact is either installed incorrectly or is not a bootstrappable tool." + exit 1 + } + $BinPath = Get-Content "$BinPathFile" + $ToolPath = Convert-Path -Path $BinPath + Write-Host "Adding $ToolName to the path ($ToolPath)..." + Write-Host "##vso[task.prependpath]$ToolPath" + $env:PATH = "$ToolPath;$env:PATH" + $InstalledTools += @{ $ToolName = $ToolDirectory.FullName } + } + } + return $InstalledTools + } else { + $NativeTools.PSObject.Properties | ForEach-Object { + $ToolName = $_.Name + $ToolVersion = $_.Value + + if ((Get-Command "$ToolName" -ErrorAction SilentlyContinue) -eq $null) { + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message "$ToolName not found on path. Please install $ToolName $ToolVersion before proceeding." + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message "If this is running on a build machine, the arcade-tools directory was not found, which means there's an error with the image." + } + } + exit 0 + } + } else { + $NativeTools.PSObject.Properties | ForEach-Object { + $ToolName = $_.Name + $ToolVersion = $_.Value + $LocalInstallerArguments = @{ ToolName = "$ToolName" } + $LocalInstallerArguments += @{ InstallPath = "$InstallBin" } + $LocalInstallerArguments += @{ BaseUri = "$BaseUri" } + $LocalInstallerArguments += @{ CommonLibraryDirectory = "$EngCommonBaseDir" } + $LocalInstallerArguments += @{ Version = "$ToolVersion" } + + if ($Verbose) { + $LocalInstallerArguments += @{ Verbose = $True } + } + if (Get-Variable 'Force' -ErrorAction 'SilentlyContinue') { + if($Force) { + $LocalInstallerArguments += @{ Force = $True } + } + } + if ($Clean) { + $LocalInstallerArguments += @{ Clean = $True } + } + + Write-Verbose "Installing $ToolName version $ToolVersion" + Write-Verbose "Executing '$InstallerPath $($LocalInstallerArguments.Keys.ForEach({"-$_ '$($LocalInstallerArguments.$_)'"}) -join ' ')'" + & $InstallerPath @LocalInstallerArguments + if ($LASTEXITCODE -Ne "0") { + $errMsg = "$ToolName installation failed" + if ((Get-Variable 'DoNotAbortNativeToolsInstallationOnFailure' -ErrorAction 'SilentlyContinue') -and $DoNotAbortNativeToolsInstallationOnFailure) { + $showNativeToolsWarning = $true + if ((Get-Variable 'DoNotDisplayNativeToolsInstallationWarnings' -ErrorAction 'SilentlyContinue') -and $DoNotDisplayNativeToolsInstallationWarnings) { + $showNativeToolsWarning = $false + } + if ($showNativeToolsWarning) { + Write-Warning $errMsg + } + $toolInstallationFailure = $true + } else { + # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 + Write-Host $errMsg + exit 1 + } + } + } + + if ((Get-Variable 'toolInstallationFailure' -ErrorAction 'SilentlyContinue') -and $toolInstallationFailure) { + # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 + Write-Host 'Native tools bootstrap failed' + exit 1 + } + } + } + else { + Write-Host 'No native tools defined in global.json' + exit 0 + } + + if ($Clean) { + exit 0 + } + if (Test-Path $InstallBin) { + Write-Host 'Native tools are available from ' (Convert-Path -Path $InstallBin) + Write-Host "##vso[task.prependpath]$(Convert-Path -Path $InstallBin)" + return $InstallBin + } + elseif (-not ($PathPromotion)) { + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message 'Native tools install directory does not exist, installation failed' + exit 1 + } + exit 0 +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message $_ + ExitWithExitCode 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/init-tools-native.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/init-tools-native.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/init-tools-native.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/init-tools-native.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,238 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +base_uri='https://netcorenativeassets.blob.core.windows.net/resource-packages/external' +install_directory='' +clean=false +force=false +download_retries=5 +retry_wait_time_seconds=30 +global_json_file="$(dirname "$(dirname "${scriptroot}")")/global.json" +declare -a native_assets + +. $scriptroot/pipeline-logging-functions.sh +. $scriptroot/native/common-library.sh + +while (($# > 0)); do + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + --baseuri) + base_uri=$2 + shift 2 + ;; + --installdirectory) + install_directory=$2 + shift 2 + ;; + --clean) + clean=true + shift 1 + ;; + --force) + force=true + shift 1 + ;; + --donotabortonfailure) + donotabortonfailure=true + shift 1 + ;; + --donotdisplaywarnings) + donotdisplaywarnings=true + shift 1 + ;; + --downloadretries) + download_retries=$2 + shift 2 + ;; + --retrywaittimeseconds) + retry_wait_time_seconds=$2 + shift 2 + ;; + --help) + echo "Common settings:" + echo " --installdirectory Directory to install native toolset." + echo " This is a command-line override for the default" + echo " Install directory precedence order:" + echo " - InstallDirectory command-line override" + echo " - NETCOREENG_INSTALL_DIRECTORY environment variable" + echo " - (default) %USERPROFILE%/.netcoreeng/native" + echo "" + echo " --clean Switch specifying not to install anything, but cleanup native asset folders" + echo " --donotabortonfailure Switch specifiying whether to abort native tools installation on failure" + echo " --donotdisplaywarnings Switch specifiying whether to display warnings during native tools installation on failure" + echo " --force Clean and then install tools" + echo " --help Print help and exit" + echo "" + echo "Advanced settings:" + echo " --baseuri Base URI for where to download native tools from" + echo " --downloadretries Number of times a download should be attempted" + echo " --retrywaittimeseconds Wait time between download attempts" + echo "" + exit 0 + ;; + esac +done + +function ReadGlobalJsonNativeTools { + # happy path: we have a proper JSON parsing tool `jq(1)` in PATH! + if command -v jq &> /dev/null; then + + # jq: read each key/value pair under "native-tools" entry and emit: + # KEY="" VALUE="" + # followed by a null byte. + # + # bash: read line with null byte delimeter and push to array (for later `eval`uation). + + while IFS= read -rd '' line; do + native_assets+=("$line") + done < <(jq -r '. | + select(has("native-tools")) | + ."native-tools" | + keys[] as $k | + @sh "KEY=\($k) VALUE=\(.[$k])\u0000"' "$global_json_file") + + return + fi + + # Warning: falling back to manually parsing JSON, which is not recommended. + + # Following routine matches the output and escaping logic of jq(1)'s @sh formatter used above. + # It has been tested with several weird strings with escaped characters in entries (key and value) + # and results were compared with the output of jq(1) in binary representation using xxd(1); + # just before the assignment to 'native_assets' array (above and below). + + # try to capture the section under "native-tools". + if [[ ! "$(cat "$global_json_file")" =~ \"native-tools\"[[:space:]\:\{]*([^\}]+) ]]; then + return + fi + + section="${BASH_REMATCH[1]}" + + parseStarted=0 + possibleEnd=0 + escaping=0 + escaped=0 + isKey=1 + + for (( i=0; i<${#section}; i++ )); do + char="${section:$i:1}" + if ! ((parseStarted)) && [[ "$char" =~ [[:space:],:] ]]; then continue; fi + + if ! ((escaping)) && [[ "$char" == "\\" ]]; then + escaping=1 + elif ((escaping)) && ! ((escaped)); then + escaped=1 + fi + + if ! ((parseStarted)) && [[ "$char" == "\"" ]]; then + parseStarted=1 + possibleEnd=0 + elif [[ "$char" == "'" ]]; then + token="$token'\\\''" + possibleEnd=0 + elif ((escaping)) || [[ "$char" != "\"" ]]; then + token="$token$char" + possibleEnd=1 + fi + + if ((possibleEnd)) && ! ((escaping)) && [[ "$char" == "\"" ]]; then + # Use printf to unescape token to match jq(1)'s @sh formatting rules. + # do not use 'token="$(printf "$token")"' syntax, as $() eats the trailing linefeed. + printf -v token "'$token'" + + if ((isKey)); then + KEY="$token" + isKey=0 + else + line="KEY=$KEY VALUE=$token" + native_assets+=("$line") + isKey=1 + fi + + # reset for next token + parseStarted=0 + token= + elif ((escaping)) && ((escaped)); then + escaping=0 + escaped=0 + fi + done +} + +native_base_dir=$install_directory +if [[ -z $install_directory ]]; then + native_base_dir=$(GetNativeInstallDirectory) +fi + +install_bin="${native_base_dir}/bin" +installed_any=false + +ReadGlobalJsonNativeTools + +if [[ ${#native_assets[@]} -eq 0 ]]; then + echo "No native tools defined in global.json" + exit 0; +else + native_installer_dir="$scriptroot/native" + for index in "${!native_assets[@]}"; do + eval "${native_assets["$index"]}" + + installer_path="$native_installer_dir/install-$KEY.sh" + installer_command="$installer_path" + installer_command+=" --baseuri $base_uri" + installer_command+=" --installpath $install_bin" + installer_command+=" --version $VALUE" + echo $installer_command + + if [[ $force = true ]]; then + installer_command+=" --force" + fi + + if [[ $clean = true ]]; then + installer_command+=" --clean" + fi + + if [[ -a $installer_path ]]; then + $installer_command + if [[ $? != 0 ]]; then + if [[ $donotabortonfailure = true ]]; then + if [[ $donotdisplaywarnings != true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed" + fi + else + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed" + exit 1 + fi + else + $installed_any = true + fi + else + if [[ $donotabortonfailure == true ]]; then + if [[ $donotdisplaywarnings != true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed: no install script" + fi + else + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed: no install script" + exit 1 + fi + fi + done +fi + +if [[ $clean = true ]]; then + exit 0 +fi + +if [[ -d $install_bin ]]; then + echo "Native tools are available from $install_bin" + echo "##vso[task.prependpath]$install_bin" +else + if [[ $installed_any = true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Native tools install directory does not exist, installation failed" + exit 1 + fi +fi + +exit 0 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/internal/Directory.Build.props dotnet8-8.0.100-8.0.0/src/aspire/eng/common/internal/Directory.Build.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/internal/Directory.Build.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/internal/Directory.Build.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,4 @@ + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/internal/NuGet.config dotnet8-8.0.100-8.0.0/src/aspire/eng/common/internal/NuGet.config --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/internal/NuGet.config 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/internal/NuGet.config 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,7 @@ + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/internal/Tools.csproj dotnet8-8.0.100-8.0.0/src/aspire/eng/common/internal/Tools.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/internal/Tools.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/internal/Tools.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,30 @@ + + + + net472 + false + false + + + + + + + + + + + + + + https://devdiv.pkgs.visualstudio.com/_packaging/dotnet-core-internal-tooling/nuget/v3/index.json; + + + $(RestoreSources); + https://devdiv.pkgs.visualstudio.com/_packaging/VS/nuget/v3/index.json; + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/internal-feed-operations.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/internal-feed-operations.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/internal-feed-operations.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/internal-feed-operations.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,132 @@ +param( + [Parameter(Mandatory=$true)][string] $Operation, + [string] $AuthToken, + [string] $CommitSha, + [string] $RepoName, + [switch] $IsFeedPrivate +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +. $PSScriptRoot\tools.ps1 + +# Sets VSS_NUGET_EXTERNAL_FEED_ENDPOINTS based on the "darc-int-*" feeds defined in NuGet.config. This is needed +# in build agents by CredProvider to authenticate the restore requests to internal feeds as specified in +# https://github.com/microsoft/artifacts-credprovider/blob/0f53327cd12fd893d8627d7b08a2171bf5852a41/README.md#environment-variables. This should ONLY be called from identified +# internal builds +function SetupCredProvider { + param( + [string] $AuthToken + ) + + # Install the Cred Provider NuGet plugin + Write-Host 'Setting up Cred Provider NuGet plugin in the agent...' + Write-Host "Getting 'installcredprovider.ps1' from 'https://github.com/microsoft/artifacts-credprovider'..." + + $url = 'https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.ps1' + + Write-Host "Writing the contents of 'installcredprovider.ps1' locally..." + Invoke-WebRequest $url -OutFile installcredprovider.ps1 + + Write-Host 'Installing plugin...' + .\installcredprovider.ps1 -Force + + Write-Host "Deleting local copy of 'installcredprovider.ps1'..." + Remove-Item .\installcredprovider.ps1 + + if (-Not("$env:USERPROFILE\.nuget\plugins\netcore")) { + Write-PipelineTelemetryError -Category 'Arcade' -Message 'CredProvider plugin was not installed correctly!' + ExitWithExitCode 1 + } + else { + Write-Host 'CredProvider plugin was installed correctly!' + } + + # Then, we set the 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' environment variable to restore from the stable + # feeds successfully + + $nugetConfigPath = Join-Path $RepoRoot "NuGet.config" + + if (-Not (Test-Path -Path $nugetConfigPath)) { + Write-PipelineTelemetryError -Category 'Build' -Message 'NuGet.config file not found in repo root!' + ExitWithExitCode 1 + } + + $endpoints = New-Object System.Collections.ArrayList + $nugetConfigPackageSources = Select-Xml -Path $nugetConfigPath -XPath "//packageSources/add[contains(@key, 'darc-int-')]/@value" | foreach{$_.Node.Value} + + if (($nugetConfigPackageSources | Measure-Object).Count -gt 0 ) { + foreach ($stableRestoreResource in $nugetConfigPackageSources) { + $trimmedResource = ([string]$stableRestoreResource).Trim() + [void]$endpoints.Add(@{endpoint="$trimmedResource"; password="$AuthToken"}) + } + } + + if (($endpoints | Measure-Object).Count -gt 0) { + $endpointCredentials = @{endpointCredentials=$endpoints} | ConvertTo-Json -Compress + + # Create the environment variables the AzDo way + Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $endpointCredentials -Properties @{ + 'variable' = 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' + 'issecret' = 'false' + } + + # We don't want sessions cached since we will be updating the endpoints quite frequently + Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data 'False' -Properties @{ + 'variable' = 'NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED' + 'issecret' = 'false' + } + } + else + { + Write-Host 'No internal endpoints found in NuGet.config' + } +} + +#Workaround for https://github.com/microsoft/msbuild/issues/4430 +function InstallDotNetSdkAndRestoreArcade { + $dotnetTempDir = Join-Path $RepoRoot "dotnet" + $dotnetSdkVersion="2.1.507" # After experimentation we know this version works when restoring the SDK (compared to 3.0.*) + $dotnet = "$dotnetTempDir\dotnet.exe" + $restoreProjPath = "$PSScriptRoot\restore.proj" + + Write-Host "Installing dotnet SDK version $dotnetSdkVersion to restore Arcade SDK..." + InstallDotNetSdk "$dotnetTempDir" "$dotnetSdkVersion" + + '' | Out-File "$restoreProjPath" + + & $dotnet restore $restoreProjPath + + Write-Host 'Arcade SDK restored!' + + if (Test-Path -Path $restoreProjPath) { + Remove-Item $restoreProjPath + } + + if (Test-Path -Path $dotnetTempDir) { + Remove-Item $dotnetTempDir -Recurse + } +} + +try { + Push-Location $PSScriptRoot + + if ($Operation -like 'setup') { + SetupCredProvider $AuthToken + } + elseif ($Operation -like 'install-restore') { + InstallDotNetSdkAndRestoreArcade + } + else { + Write-PipelineTelemetryError -Category 'Arcade' -Message "Unknown operation '$Operation'!" + ExitWithExitCode 1 + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Arcade' -Message $_ + ExitWithExitCode 1 +} +finally { + Pop-Location +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/internal-feed-operations.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/internal-feed-operations.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/internal-feed-operations.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/internal-feed-operations.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,141 @@ +#!/usr/bin/env bash + +set -e + +# Sets VSS_NUGET_EXTERNAL_FEED_ENDPOINTS based on the "darc-int-*" feeds defined in NuGet.config. This is needed +# in build agents by CredProvider to authenticate the restore requests to internal feeds as specified in +# https://github.com/microsoft/artifacts-credprovider/blob/0f53327cd12fd893d8627d7b08a2171bf5852a41/README.md#environment-variables. +# This should ONLY be called from identified internal builds +function SetupCredProvider { + local authToken=$1 + + # Install the Cred Provider NuGet plugin + echo "Setting up Cred Provider NuGet plugin in the agent..."... + echo "Getting 'installcredprovider.ps1' from 'https://github.com/microsoft/artifacts-credprovider'..." + + local url="https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh" + + echo "Writing the contents of 'installcredprovider.ps1' locally..." + local installcredproviderPath="installcredprovider.sh" + if command -v curl > /dev/null; then + curl $url > "$installcredproviderPath" + else + wget -q -O "$installcredproviderPath" "$url" + fi + + echo "Installing plugin..." + . "$installcredproviderPath" + + echo "Deleting local copy of 'installcredprovider.sh'..." + rm installcredprovider.sh + + if [ ! -d "$HOME/.nuget/plugins" ]; then + Write-PipelineTelemetryError -category 'Build' 'CredProvider plugin was not installed correctly!' + ExitWithExitCode 1 + else + echo "CredProvider plugin was installed correctly!" + fi + + # Then, we set the 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' environment variable to restore from the stable + # feeds successfully + + local nugetConfigPath="{$repo_root}NuGet.config" + + if [ ! "$nugetConfigPath" ]; then + Write-PipelineTelemetryError -category 'Build' "NuGet.config file not found in repo's root!" + ExitWithExitCode 1 + fi + + local endpoints='[' + local nugetConfigPackageValues=`cat "$nugetConfigPath" | grep "key=\"darc-int-"` + local pattern="value=\"(.*)\"" + + for value in $nugetConfigPackageValues + do + if [[ $value =~ $pattern ]]; then + local endpoint="${BASH_REMATCH[1]}" + endpoints+="{\"endpoint\": \"$endpoint\", \"password\": \"$authToken\"}," + fi + done + + endpoints=${endpoints%?} + endpoints+=']' + + if [ ${#endpoints} -gt 2 ]; then + local endpointCredentials="{\"endpointCredentials\": "$endpoints"}" + + echo "##vso[task.setvariable variable=VSS_NUGET_EXTERNAL_FEED_ENDPOINTS]$endpointCredentials" + echo "##vso[task.setvariable variable=NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED]False" + else + echo "No internal endpoints found in NuGet.config" + fi +} + +# Workaround for https://github.com/microsoft/msbuild/issues/4430 +function InstallDotNetSdkAndRestoreArcade { + local dotnetTempDir="$repo_root/dotnet" + local dotnetSdkVersion="2.1.507" # After experimentation we know this version works when restoring the SDK (compared to 3.0.*) + local restoreProjPath="$repo_root/eng/common/restore.proj" + + echo "Installing dotnet SDK version $dotnetSdkVersion to restore Arcade SDK..." + echo "" > "$restoreProjPath" + + InstallDotNetSdk "$dotnetTempDir" "$dotnetSdkVersion" + + local res=`$dotnetTempDir/dotnet restore $restoreProjPath` + echo "Arcade SDK restored!" + + # Cleanup + if [ "$restoreProjPath" ]; then + rm "$restoreProjPath" + fi + + if [ "$dotnetTempDir" ]; then + rm -r $dotnetTempDir + fi +} + +source="${BASH_SOURCE[0]}" +operation='' +authToken='' +repoName='' + +while [[ $# > 0 ]]; do + opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + --operation) + operation=$2 + shift + ;; + --authtoken) + authToken=$2 + shift + ;; + *) + echo "Invalid argument: $1" + usage + exit 1 + ;; + esac + + shift +done + +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/tools.sh" + +if [ "$operation" = "setup" ]; then + SetupCredProvider $authToken +elif [ "$operation" = "install-restore" ]; then + InstallDotNetSdkAndRestoreArcade +else + echo "Unknown operation '$operation'!" +fi diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/loc/P22DotNetHtmlLocalization.lss dotnet8-8.0.100-8.0.0/src/aspire/eng/common/loc/P22DotNetHtmlLocalization.lss --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/loc/P22DotNetHtmlLocalization.lss 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/loc/P22DotNetHtmlLocalization.lss 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/msbuild.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/msbuild.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/msbuild.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/msbuild.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,28 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string] $verbosity = 'minimal', + [bool] $warnAsError = $true, + [bool] $nodeReuse = $true, + [switch] $ci, + [switch] $prepareMachine, + [switch] $excludePrereleaseVS, + [string] $msbuildEngine = $null, + [Parameter(ValueFromRemainingArguments=$true)][String[]]$extraArgs +) + +. $PSScriptRoot\tools.ps1 + +try { + if ($ci) { + $nodeReuse = $false + } + + MSBuild @extraArgs +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Build' -Message $_ + ExitWithExitCode 1 +} + +ExitWithExitCode 0 \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/msbuild.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/msbuild.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/msbuild.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/msbuild.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +verbosity='minimal' +warn_as_error=true +node_reuse=true +prepare_machine=false +extra_args='' + +while (($# > 0)); do + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + --verbosity) + verbosity=$2 + shift 2 + ;; + --warnaserror) + warn_as_error=$2 + shift 2 + ;; + --nodereuse) + node_reuse=$2 + shift 2 + ;; + --ci) + ci=true + shift 1 + ;; + --preparemachine) + prepare_machine=true + shift 1 + ;; + *) + extra_args="$extra_args $1" + shift 1 + ;; + esac +done + +. "$scriptroot/tools.sh" + +if [[ "$ci" == true ]]; then + node_reuse=false +fi + +MSBuild $extra_args +ExitWithExitCode 0 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/CommonLibrary.psm1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/CommonLibrary.psm1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/CommonLibrary.psm1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/CommonLibrary.psm1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,400 @@ +<# +.SYNOPSIS +Helper module to install an archive to a directory + +.DESCRIPTION +Helper module to download and extract an archive to a specified directory + +.PARAMETER Uri +Uri of artifact to download + +.PARAMETER InstallDirectory +Directory to extract artifact contents to + +.PARAMETER Force +Force download / extraction if file or contents already exist. Default = False + +.PARAMETER DownloadRetries +Total number of retry attempts. Default = 5 + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds. Default = 30 + +.NOTES +Returns False if download or extraction fail, True otherwise +#> +function DownloadAndExtract { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $Uri, + [Parameter(Mandatory=$True)] + [string] $InstallDirectory, + [switch] $Force = $False, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30 + ) + # Define verbose switch if undefined + $Verbose = $VerbosePreference -Eq "Continue" + + $TempToolPath = CommonLibrary\Get-TempPathFilename -Path $Uri + + # Download native tool + $DownloadStatus = CommonLibrary\Get-File -Uri $Uri ` + -Path $TempToolPath ` + -DownloadRetries $DownloadRetries ` + -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` + -Force:$Force ` + -Verbose:$Verbose + + if ($DownloadStatus -Eq $False) { + Write-Error "Download failed from $Uri" + return $False + } + + # Extract native tool + $UnzipStatus = CommonLibrary\Expand-Zip -ZipPath $TempToolPath ` + -OutputDirectory $InstallDirectory ` + -Force:$Force ` + -Verbose:$Verbose + + if ($UnzipStatus -Eq $False) { + # Retry Download one more time with Force=true + $DownloadRetryStatus = CommonLibrary\Get-File -Uri $Uri ` + -Path $TempToolPath ` + -DownloadRetries 1 ` + -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` + -Force:$True ` + -Verbose:$Verbose + + if ($DownloadRetryStatus -Eq $False) { + Write-Error "Last attempt of download failed as well" + return $False + } + + # Retry unzip again one more time with Force=true + $UnzipRetryStatus = CommonLibrary\Expand-Zip -ZipPath $TempToolPath ` + -OutputDirectory $InstallDirectory ` + -Force:$True ` + -Verbose:$Verbose + if ($UnzipRetryStatus -Eq $False) + { + Write-Error "Last attempt of unzip failed as well" + # Clean up partial zips and extracts + if (Test-Path $TempToolPath) { + Remove-Item $TempToolPath -Force + } + if (Test-Path $InstallDirectory) { + Remove-Item $InstallDirectory -Force -Recurse + } + return $False + } + } + + return $True +} + +<# +.SYNOPSIS +Download a file, retry on failure + +.DESCRIPTION +Download specified file and retry if attempt fails + +.PARAMETER Uri +Uri of file to download. If Uri is a local path, the file will be copied instead of downloaded + +.PARAMETER Path +Path to download or copy uri file to + +.PARAMETER Force +Overwrite existing file if present. Default = False + +.PARAMETER DownloadRetries +Total number of retry attempts. Default = 5 + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds Default = 30 + +#> +function Get-File { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $Uri, + [Parameter(Mandatory=$True)] + [string] $Path, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30, + [switch] $Force = $False + ) + $Attempt = 0 + + if ($Force) { + if (Test-Path $Path) { + Remove-Item $Path -Force + } + } + if (Test-Path $Path) { + Write-Host "File '$Path' already exists, skipping download" + return $True + } + + $DownloadDirectory = Split-Path -ErrorAction Ignore -Path "$Path" -Parent + if (-Not (Test-Path $DownloadDirectory)) { + New-Item -path $DownloadDirectory -force -itemType "Directory" | Out-Null + } + + $TempPath = "$Path.tmp" + if (Test-Path -IsValid -Path $Uri) { + Write-Verbose "'$Uri' is a file path, copying temporarily to '$TempPath'" + Copy-Item -Path $Uri -Destination $TempPath + Write-Verbose "Moving temporary file to '$Path'" + Move-Item -Path $TempPath -Destination $Path + return $? + } + else { + Write-Verbose "Downloading $Uri" + # Don't display the console progress UI - it's a huge perf hit + $ProgressPreference = 'SilentlyContinue' + while($Attempt -Lt $DownloadRetries) + { + try { + Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $TempPath + Write-Verbose "Downloaded to temporary location '$TempPath'" + Move-Item -Path $TempPath -Destination $Path + Write-Verbose "Moved temporary file to '$Path'" + return $True + } + catch { + $Attempt++ + if ($Attempt -Lt $DownloadRetries) { + $AttemptsLeft = $DownloadRetries - $Attempt + Write-Warning "Download failed, $AttemptsLeft attempts remaining, will retry in $RetryWaitTimeInSeconds seconds" + Start-Sleep -Seconds $RetryWaitTimeInSeconds + } + else { + Write-Error $_ + Write-Error $_.Exception + } + } + } + } + + return $False +} + +<# +.SYNOPSIS +Generate a shim for a native tool + +.DESCRIPTION +Creates a wrapper script (shim) that passes arguments forward to native tool assembly + +.PARAMETER ShimName +The name of the shim + +.PARAMETER ShimDirectory +The directory where shims are stored + +.PARAMETER ToolFilePath +Path to file that shim forwards to + +.PARAMETER Force +Replace shim if already present. Default = False + +.NOTES +Returns $True if generating shim succeeds, $False otherwise +#> +function New-ScriptShim { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $ShimName, + [Parameter(Mandatory=$True)] + [string] $ShimDirectory, + [Parameter(Mandatory=$True)] + [string] $ToolFilePath, + [Parameter(Mandatory=$True)] + [string] $BaseUri, + [switch] $Force + ) + try { + Write-Verbose "Generating '$ShimName' shim" + + if (-Not (Test-Path $ToolFilePath)){ + Write-Error "Specified tool file path '$ToolFilePath' does not exist" + return $False + } + + # WinShimmer is a small .NET Framework program that creates .exe shims to bootstrapped programs + # Many of the checks for installed programs expect a .exe extension for Windows tools, rather + # than a .bat or .cmd file. + # Source: https://github.com/dotnet/arcade/tree/master/src/WinShimmer + if (-Not (Test-Path "$ShimDirectory\WinShimmer\winshimmer.exe")) { + $InstallStatus = DownloadAndExtract -Uri "$BaseUri/windows/winshimmer/WinShimmer.zip" ` + -InstallDirectory $ShimDirectory\WinShimmer ` + -Force:$Force ` + -DownloadRetries 2 ` + -RetryWaitTimeInSeconds 5 ` + -Verbose:$Verbose + } + + if ((Test-Path (Join-Path $ShimDirectory "$ShimName.exe"))) { + Write-Host "$ShimName.exe already exists; replacing..." + Remove-Item (Join-Path $ShimDirectory "$ShimName.exe") + } + + & "$ShimDirectory\WinShimmer\winshimmer.exe" $ShimName $ToolFilePath $ShimDirectory + return $True + } + catch { + Write-Host $_ + Write-Host $_.Exception + return $False + } +} + +<# +.SYNOPSIS +Returns the machine architecture of the host machine + +.NOTES +Returns 'x64' on 64 bit machines + Returns 'x86' on 32 bit machines +#> +function Get-MachineArchitecture { + $ProcessorArchitecture = $Env:PROCESSOR_ARCHITECTURE + $ProcessorArchitectureW6432 = $Env:PROCESSOR_ARCHITEW6432 + if($ProcessorArchitecture -Eq "X86") + { + if(($ProcessorArchitectureW6432 -Eq "") -Or + ($ProcessorArchitectureW6432 -Eq "X86")) { + return "x86" + } + $ProcessorArchitecture = $ProcessorArchitectureW6432 + } + if (($ProcessorArchitecture -Eq "AMD64") -Or + ($ProcessorArchitecture -Eq "IA64") -Or + ($ProcessorArchitecture -Eq "ARM64") -Or + ($ProcessorArchitecture -Eq "LOONGARCH64")) { + return "x64" + } + return "x86" +} + +<# +.SYNOPSIS +Get the name of a temporary folder under the native install directory +#> +function Get-TempDirectory { + return Join-Path (Get-NativeInstallDirectory) "temp/" +} + +function Get-TempPathFilename { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $Path + ) + $TempDir = CommonLibrary\Get-TempDirectory + $TempFilename = Split-Path $Path -leaf + $TempPath = Join-Path $TempDir $TempFilename + return $TempPath +} + +<# +.SYNOPSIS +Returns the base directory to use for native tool installation + +.NOTES +Returns the value of the NETCOREENG_INSTALL_DIRECTORY if that environment variable +is set, or otherwise returns an install directory under the %USERPROFILE% +#> +function Get-NativeInstallDirectory { + $InstallDir = $Env:NETCOREENG_INSTALL_DIRECTORY + if (!$InstallDir) { + $InstallDir = Join-Path $Env:USERPROFILE ".netcoreeng/native/" + } + return $InstallDir +} + +<# +.SYNOPSIS +Unzip an archive + +.DESCRIPTION +Powershell module to unzip an archive to a specified directory + +.PARAMETER ZipPath (Required) +Path to archive to unzip + +.PARAMETER OutputDirectory (Required) +Output directory for archive contents + +.PARAMETER Force +Overwrite output directory contents if they already exist + +.NOTES +- Returns True and does not perform an extraction if output directory already exists but Overwrite is not True. +- Returns True if unzip operation is successful +- Returns False if Overwrite is True and it is unable to remove contents of OutputDirectory +- Returns False if unable to extract zip archive +#> +function Expand-Zip { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $ZipPath, + [Parameter(Mandatory=$True)] + [string] $OutputDirectory, + [switch] $Force + ) + + Write-Verbose "Extracting '$ZipPath' to '$OutputDirectory'" + try { + if ((Test-Path $OutputDirectory) -And (-Not $Force)) { + Write-Host "Directory '$OutputDirectory' already exists, skipping extract" + return $True + } + if (Test-Path $OutputDirectory) { + Write-Verbose "'Force' is 'True', but '$OutputDirectory' exists, removing directory" + Remove-Item $OutputDirectory -Force -Recurse + if ($? -Eq $False) { + Write-Error "Unable to remove '$OutputDirectory'" + return $False + } + } + + $TempOutputDirectory = Join-Path "$(Split-Path -Parent $OutputDirectory)" "$(Split-Path -Leaf $OutputDirectory).tmp" + if (Test-Path $TempOutputDirectory) { + Remove-Item $TempOutputDirectory -Force -Recurse + } + New-Item -Path $TempOutputDirectory -Force -ItemType "Directory" | Out-Null + + Add-Type -assembly "system.io.compression.filesystem" + [io.compression.zipfile]::ExtractToDirectory("$ZipPath", "$TempOutputDirectory") + if ($? -Eq $False) { + Write-Error "Unable to extract '$ZipPath'" + return $False + } + + Move-Item -Path $TempOutputDirectory -Destination $OutputDirectory + } + catch { + Write-Host $_ + Write-Host $_.Exception + + return $False + } + return $True +} + +export-modulemember -function DownloadAndExtract +export-modulemember -function Expand-Zip +export-modulemember -function Get-File +export-modulemember -function Get-MachineArchitecture +export-modulemember -function Get-NativeInstallDirectory +export-modulemember -function Get-TempDirectory +export-modulemember -function Get-TempPathFilename +export-modulemember -function New-ScriptShim diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/common-library.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/common-library.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/common-library.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/common-library.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,172 @@ +#!/usr/bin/env bash + +function GetNativeInstallDirectory { + local install_dir + + if [[ -z $NETCOREENG_INSTALL_DIRECTORY ]]; then + install_dir=$HOME/.netcoreeng/native/ + else + install_dir=$NETCOREENG_INSTALL_DIRECTORY + fi + + echo $install_dir + return 0 +} + +function GetTempDirectory { + + echo $(GetNativeInstallDirectory)temp/ + return 0 +} + +function ExpandZip { + local zip_path=$1 + local output_directory=$2 + local force=${3:-false} + + echo "Extracting $zip_path to $output_directory" + if [[ -d $output_directory ]] && [[ $force = false ]]; then + echo "Directory '$output_directory' already exists, skipping extract" + return 0 + fi + + if [[ -d $output_directory ]]; then + echo "'Force flag enabled, but '$output_directory' exists. Removing directory" + rm -rf $output_directory + if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to remove '$output_directory'" + return 1 + fi + fi + + echo "Creating directory: '$output_directory'" + mkdir -p $output_directory + + echo "Extracting archive" + tar -xf $zip_path -C $output_directory + if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to extract '$zip_path'" + return 1 + fi + + return 0 +} + +function GetCurrentOS { + local unameOut="$(uname -s)" + case $unameOut in + Linux*) echo "Linux";; + Darwin*) echo "MacOS";; + esac + return 0 +} + +function GetFile { + local uri=$1 + local path=$2 + local force=${3:-false} + local download_retries=${4:-5} + local retry_wait_time_seconds=${5:-30} + + if [[ -f $path ]]; then + if [[ $force = false ]]; then + echo "File '$path' already exists. Skipping download" + return 0 + else + rm -rf $path + fi + fi + + if [[ -f $uri ]]; then + echo "'$uri' is a file path, copying file to '$path'" + cp $uri $path + return $? + fi + + echo "Downloading $uri" + # Use curl if available, otherwise use wget + if command -v curl > /dev/null; then + curl "$uri" -sSL --retry $download_retries --retry-delay $retry_wait_time_seconds --create-dirs -o "$path" --fail + else + wget -q -O "$path" "$uri" --tries="$download_retries" + fi + + return $? +} + +function GetTempPathFileName { + local path=$1 + + local temp_dir=$(GetTempDirectory) + local temp_file_name=$(basename $path) + echo $temp_dir$temp_file_name + return 0 +} + +function DownloadAndExtract { + local uri=$1 + local installDir=$2 + local force=${3:-false} + local download_retries=${4:-5} + local retry_wait_time_seconds=${5:-30} + + local temp_tool_path=$(GetTempPathFileName $uri) + + echo "downloading to: $temp_tool_path" + + # Download file + GetFile "$uri" "$temp_tool_path" $force $download_retries $retry_wait_time_seconds + if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to download '$uri' to '$temp_tool_path'." + return 1 + fi + + # Extract File + echo "extracting from $temp_tool_path to $installDir" + ExpandZip "$temp_tool_path" "$installDir" $force $download_retries $retry_wait_time_seconds + if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to extract '$temp_tool_path' to '$installDir'." + return 1 + fi + + return 0 +} + +function NewScriptShim { + local shimpath=$1 + local tool_file_path=$2 + local force=${3:-false} + + echo "Generating '$shimpath' shim" + if [[ -f $shimpath ]]; then + if [[ $force = false ]]; then + echo "File '$shimpath' already exists." >&2 + return 1 + else + rm -rf $shimpath + fi + fi + + if [[ ! -f $tool_file_path ]]; then + # try to see if the path is lower cased + tool_file_path="$(echo $tool_file_path | tr "[:upper:]" "[:lower:]")" + if [[ ! -f $tool_file_path ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Specified tool file path:'$tool_file_path' does not exist" + return 1 + fi + fi + + local shim_contents=$'#!/usr/bin/env bash\n' + shim_contents+="SHIMARGS="$'$1\n' + shim_contents+="$tool_file_path"$' $SHIMARGS\n' + + # Write shim file + echo "$shim_contents" > $shimpath + + chmod +x $shimpath + + echo "Finished generating shim '$shimpath'" + + return $? +} + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/init-compiler.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/init-compiler.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/init-compiler.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/init-compiler.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,137 @@ +#!/bin/sh +# +# This file detects the C/C++ compiler and exports it to the CC/CXX environment variables +# +# NOTE: some scripts source this file and rely on stdout being empty, make sure to not output anything here! + +if [ -z "$build_arch" ] || [ -z "$compiler" ]; then + echo "Usage..." + echo "build_arch= compiler= init-compiler.sh" + echo "Specify the target architecture." + echo "Specify the name of compiler (clang or gcc)." + exit 1 +fi + +case "$compiler" in + clang*|-clang*|--clang*) + # clangx.y or clang-x.y + version="$(echo "$compiler" | tr -d '[:alpha:]-=')" + majorVersion="${version%%.*}" + [ -z "${version##*.*}" ] && minorVersion="${version#*.}" + + if [ -z "$minorVersion" ] && [ -n "$majorVersion" ] && [ "$majorVersion" -le 6 ]; then + minorVersion=0; + fi + compiler=clang + ;; + + gcc*|-gcc*|--gcc*) + # gccx.y or gcc-x.y + version="$(echo "$compiler" | tr -d '[:alpha:]-=')" + majorVersion="${version%%.*}" + [ -z "${version##*.*}" ] && minorVersion="${version#*.}" + compiler=gcc + ;; +esac + +cxxCompiler="$compiler++" + +# clear the existing CC and CXX from environment +CC= +CXX= +LDFLAGS= + +if [ "$compiler" = "gcc" ]; then cxxCompiler="g++"; fi + +check_version_exists() { + desired_version=-1 + + # Set up the environment to be used for building with the desired compiler. + if command -v "$compiler-$1.$2" > /dev/null; then + desired_version="-$1.$2" + elif command -v "$compiler$1$2" > /dev/null; then + desired_version="$1$2" + elif command -v "$compiler-$1$2" > /dev/null; then + desired_version="-$1$2" + fi + + echo "$desired_version" +} + +if [ -z "$CLR_CC" ]; then + + # Set default versions + if [ -z "$majorVersion" ]; then + # note: gcc (all versions) and clang versions higher than 6 do not have minor version in file name, if it is zero. + if [ "$compiler" = "clang" ]; then versions="17 16 15 14 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5" + elif [ "$compiler" = "gcc" ]; then versions="13 12 11 10 9 8 7 6 5 4.9"; fi + + for version in $versions; do + _major="${version%%.*}" + [ -z "${version##*.*}" ] && _minor="${version#*.}" + desired_version="$(check_version_exists "$_major" "$_minor")" + if [ "$desired_version" != "-1" ]; then majorVersion="$_major"; break; fi + done + + if [ -z "$majorVersion" ]; then + if command -v "$compiler" > /dev/null; then + if [ "$(uname)" != "Darwin" ]; then + echo "Warning: Specific version of $compiler not found, falling back to use the one in PATH." + fi + CC="$(command -v "$compiler")" + CXX="$(command -v "$cxxCompiler")" + else + echo "No usable version of $compiler found." + exit 1 + fi + else + if [ "$compiler" = "clang" ] && [ "$majorVersion" -lt 5 ]; then + if [ "$build_arch" = "arm" ] || [ "$build_arch" = "armel" ]; then + if command -v "$compiler" > /dev/null; then + echo "Warning: Found clang version $majorVersion which is not supported on arm/armel architectures, falling back to use clang from PATH." + CC="$(command -v "$compiler")" + CXX="$(command -v "$cxxCompiler")" + else + echo "Found clang version $majorVersion which is not supported on arm/armel architectures, and there is no clang in PATH." + exit 1 + fi + fi + fi + fi + else + desired_version="$(check_version_exists "$majorVersion" "$minorVersion")" + if [ "$desired_version" = "-1" ]; then + echo "Could not find specific version of $compiler: $majorVersion $minorVersion." + exit 1 + fi + fi + + if [ -z "$CC" ]; then + CC="$(command -v "$compiler$desired_version")" + CXX="$(command -v "$cxxCompiler$desired_version")" + if [ -z "$CXX" ]; then CXX="$(command -v "$cxxCompiler")"; fi + fi +else + if [ ! -f "$CLR_CC" ]; then + echo "CLR_CC is set but path '$CLR_CC' does not exist" + exit 1 + fi + CC="$CLR_CC" + CXX="$CLR_CXX" +fi + +if [ -z "$CC" ]; then + echo "Unable to find $compiler." + exit 1 +fi + +# Only lld version >= 9 can be considered stable. lld doesn't support s390x. +if [ "$compiler" = "clang" ] && [ -n "$majorVersion" ] && [ "$majorVersion" -ge 9 ] && [ "$build_arch" != "s390x" ]; then + if "$CC" -fuse-ld=lld -Wl,--version >/dev/null 2>&1; then + LDFLAGS="-fuse-ld=lld" + fi +fi + +SCAN_BUILD_COMMAND="$(command -v "scan-build$desired_version")" + +export CC CXX LDFLAGS SCAN_BUILD_COMMAND diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/init-distro-rid.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/init-distro-rid.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/init-distro-rid.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/init-distro-rid.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,130 @@ +#!/usr/bin/env bash + +# getNonPortableDistroRid +# +# Input: +# targetOs: (str) +# targetArch: (str) +# rootfsDir: (str) +# +# Return: +# non-portable rid +getNonPortableDistroRid() +{ + local targetOs="$1" + local targetArch="$2" + local rootfsDir="$3" + local nonPortableRid="" + + if [ "$targetOs" = "linux" ]; then + if [ -e "${rootfsDir}/etc/os-release" ]; then + source "${rootfsDir}/etc/os-release" + + if [[ "${ID}" == "rhel" || "${ID}" == "rocky" || "${ID}" == "alpine" ]]; then + # remove the last version digit + VERSION_ID="${VERSION_ID%.*}" + fi + + if [[ "${VERSION_ID:-}" =~ ^([[:digit:]]|\.)+$ ]]; then + nonPortableRid="${ID}.${VERSION_ID}-${targetArch}" + else + # Rolling release distros either do not set VERSION_ID, set it as blank or + # set it to non-version looking string (such as TEMPLATE_VERSION_ID on ArchLinux); + # so omit it here to be consistent with everything else. + nonPortableRid="${ID}-${targetArch}" + fi + + elif [ -e "${rootfsDir}/android_platform" ]; then + source "$rootfsDir"/android_platform + nonPortableRid="$RID" + fi + fi + + if [ "$targetOs" = "freebsd" ]; then + # $rootfsDir can be empty. freebsd-version is shell script and it should always work. + __freebsd_major_version=$($rootfsDir/bin/freebsd-version | { read v; echo "${v%%.*}"; }) + nonPortableRid="freebsd.$__freebsd_major_version-${targetArch}" + elif command -v getprop && getprop ro.product.system.model 2>&1 | grep -qi android; then + __android_sdk_version=$(getprop ro.build.version.sdk) + nonPortableRid="android.$__android_sdk_version-${targetArch}" + elif [ "$targetOs" = "illumos" ]; then + __uname_version=$(uname -v) + case "$__uname_version" in + omnios-*) + __omnios_major_version=$(echo "${__uname_version:8:2}") + nonPortableRid=omnios."$__omnios_major_version"-"$targetArch" + ;; + joyent_*) + __smartos_major_version=$(echo "${__uname_version:7:4}") + nonPortableRid=smartos."$__smartos_major_version"-"$targetArch" + ;; + illumos_*) + nonPortableRid=openindiana-"$targetArch" + ;; + esac + elif [ "$targetOs" = "solaris" ]; then + __uname_version=$(uname -v) + __solaris_major_version=$(echo "${__uname_version%.*}") + nonPortableRid=solaris."$__solaris_major_version"-"$targetArch" + elif [ "$targetOs" = "haiku" ]; then + __uname_release=$(uname -r) + nonPortableRid=haiku.r"$__uname_release"-"$targetArch" + fi + + echo "$(echo $nonPortableRid | tr '[:upper:]' '[:lower:]')" +} + +# initDistroRidGlobal +# +# Input: +# os: (str) +# arch: (str) +# rootfsDir?: (nullable:string) +# +# Return: +# None +# +# Notes: +# +# It is important to note that the function does not return anything, but it +# exports the following variables on success: +# +# __DistroRid : Non-portable rid of the target platform. +# __PortableTargetOS : OS-part of the portable rid that corresponds to the target platform. +# +initDistroRidGlobal() +{ + local targetOs="$1" + local targetArch="$2" + local rootfsDir="" + if [ "$#" -ge 3 ]; then + rootfsDir="$3" + fi + + if [ -n "${rootfsDir}" ]; then + # We may have a cross build. Check for the existence of the rootfsDir + if [ ! -e "${rootfsDir}" ]; then + echo "Error rootfsDir has been passed, but the location is not valid." + exit 1 + fi + fi + + __DistroRid=$(getNonPortableDistroRid "${targetOs}" "${targetArch}" "${rootfsDir}") + + if [ -z "${__PortableTargetOS:-}" ]; then + __PortableTargetOS="$targetOs" + + STRINGS="$(command -v strings || true)" + if [ -z "$STRINGS" ]; then + STRINGS="$(command -v llvm-strings || true)" + fi + + # Check for musl-based distros (e.g Alpine Linux, Void Linux). + if "${rootfsDir}/usr/bin/ldd" --version 2>&1 | grep -q musl || + ( [ -n "$STRINGS" ] && "$STRINGS" "${rootfsDir}/usr/bin/ldd" 2>&1 | grep -q musl ); then + __PortableTargetOS="linux-musl" + fi + fi + + export __DistroRid __PortableTargetOS +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/init-os-and-arch.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/init-os-and-arch.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/init-os-and-arch.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/init-os-and-arch.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +# Use uname to determine what the OS is. +OSName=$(uname -s | tr '[:upper:]' '[:lower:]') + +if command -v getprop && getprop ro.product.system.model 2>&1 | grep -qi android; then + OSName="android" +fi + +case "$OSName" in +freebsd|linux|netbsd|openbsd|sunos|android|haiku) + os="$OSName" ;; +darwin) + os=osx ;; +*) + echo "Unsupported OS $OSName detected!" + exit 1 ;; +esac + +# On Solaris, `uname -m` is discouraged, see https://docs.oracle.com/cd/E36784_01/html/E36870/uname-1.html +# and `uname -p` returns processor type (e.g. i386 on amd64). +# The appropriate tool to determine CPU is isainfo(1) https://docs.oracle.com/cd/E36784_01/html/E36870/isainfo-1.html. +if [ "$os" = "sunos" ]; then + if uname -o 2>&1 | grep -q illumos; then + os="illumos" + else + os="solaris" + fi + CPUName=$(isainfo -n) +else + # For the rest of the operating systems, use uname(1) to determine what the CPU is. + CPUName=$(uname -m) +fi + +case "$CPUName" in + arm64|aarch64) + arch=arm64 + ;; + + loongarch64) + arch=loongarch64 + ;; + + riscv64) + arch=riscv64 + ;; + + amd64|x86_64) + arch=x64 + ;; + + armv7l|armv8l) + if (NAME=""; . /etc/os-release; test "$NAME" = "Tizen"); then + arch=armel + else + arch=arm + fi + ;; + + armv6l) + arch=armv6 + ;; + + i[3-6]86) + echo "Unsupported CPU $CPUName detected, build might not succeed!" + arch=x86 + ;; + + s390x) + arch=s390x + ;; + + ppc64le) + arch=ppc64le + ;; + *) + echo "Unknown CPU $CPUName detected!" + exit 1 + ;; +esac diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/install-cmake.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/install-cmake.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/install-cmake.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/install-cmake.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. $scriptroot/common-library.sh + +base_uri= +install_path= +version= +clean=false +force=false +download_retries=5 +retry_wait_time_seconds=30 + +while (($# > 0)); do + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + --baseuri) + base_uri=$2 + shift 2 + ;; + --installpath) + install_path=$2 + shift 2 + ;; + --version) + version=$2 + shift 2 + ;; + --clean) + clean=true + shift 1 + ;; + --force) + force=true + shift 1 + ;; + --downloadretries) + download_retries=$2 + shift 2 + ;; + --retrywaittimeseconds) + retry_wait_time_seconds=$2 + shift 2 + ;; + --help) + echo "Common settings:" + echo " --baseuri Base file directory or Url wrom which to acquire tool archives" + echo " --installpath Base directory to install native tool to" + echo " --clean Don't install the tool, just clean up the current install of the tool" + echo " --force Force install of tools even if they previously exist" + echo " --help Print help and exit" + echo "" + echo "Advanced settings:" + echo " --downloadretries Total number of retry attempts" + echo " --retrywaittimeseconds Wait time between retry attempts in seconds" + echo "" + exit 0 + ;; + esac +done + +tool_name="cmake" +tool_os=$(GetCurrentOS) +tool_folder="$(echo $tool_os | tr "[:upper:]" "[:lower:]")" +tool_arch="x86_64" +tool_name_moniker="$tool_name-$version-$tool_os-$tool_arch" +tool_install_directory="$install_path/$tool_name/$version" +tool_file_path="$tool_install_directory/$tool_name_moniker/bin/$tool_name" +shim_path="$install_path/$tool_name.sh" +uri="${base_uri}/$tool_folder/$tool_name/$tool_name_moniker.tar.gz" + +# Clean up tool and installers +if [[ $clean = true ]]; then + echo "Cleaning $tool_install_directory" + if [[ -d $tool_install_directory ]]; then + rm -rf $tool_install_directory + fi + + echo "Cleaning $shim_path" + if [[ -f $shim_path ]]; then + rm -rf $shim_path + fi + + tool_temp_path=$(GetTempPathFileName $uri) + echo "Cleaning $tool_temp_path" + if [[ -f $tool_temp_path ]]; then + rm -rf $tool_temp_path + fi + + exit 0 +fi + +# Install tool +if [[ -f $tool_file_path ]] && [[ $force = false ]]; then + echo "$tool_name ($version) already exists, skipping install" + exit 0 +fi + +DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds + +if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' + exit 1 +fi + +# Generate Shim +# Always rewrite shims so that we are referencing the expected version +NewScriptShim $shim_path $tool_file_path true + +if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' + exit 1 +fi + +exit 0 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/install-cmake-test.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/install-cmake-test.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/install-cmake-test.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/install-cmake-test.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. $scriptroot/common-library.sh + +base_uri= +install_path= +version= +clean=false +force=false +download_retries=5 +retry_wait_time_seconds=30 + +while (($# > 0)); do + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + --baseuri) + base_uri=$2 + shift 2 + ;; + --installpath) + install_path=$2 + shift 2 + ;; + --version) + version=$2 + shift 2 + ;; + --clean) + clean=true + shift 1 + ;; + --force) + force=true + shift 1 + ;; + --downloadretries) + download_retries=$2 + shift 2 + ;; + --retrywaittimeseconds) + retry_wait_time_seconds=$2 + shift 2 + ;; + --help) + echo "Common settings:" + echo " --baseuri Base file directory or Url wrom which to acquire tool archives" + echo " --installpath Base directory to install native tool to" + echo " --clean Don't install the tool, just clean up the current install of the tool" + echo " --force Force install of tools even if they previously exist" + echo " --help Print help and exit" + echo "" + echo "Advanced settings:" + echo " --downloadretries Total number of retry attempts" + echo " --retrywaittimeseconds Wait time between retry attempts in seconds" + echo "" + exit 0 + ;; + esac +done + +tool_name="cmake-test" +tool_os=$(GetCurrentOS) +tool_folder="$(echo $tool_os | tr "[:upper:]" "[:lower:]")" +tool_arch="x86_64" +tool_name_moniker="$tool_name-$version-$tool_os-$tool_arch" +tool_install_directory="$install_path/$tool_name/$version" +tool_file_path="$tool_install_directory/$tool_name_moniker/bin/$tool_name" +shim_path="$install_path/$tool_name.sh" +uri="${base_uri}/$tool_folder/$tool_name/$tool_name_moniker.tar.gz" + +# Clean up tool and installers +if [[ $clean = true ]]; then + echo "Cleaning $tool_install_directory" + if [[ -d $tool_install_directory ]]; then + rm -rf $tool_install_directory + fi + + echo "Cleaning $shim_path" + if [[ -f $shim_path ]]; then + rm -rf $shim_path + fi + + tool_temp_path=$(GetTempPathFileName $uri) + echo "Cleaning $tool_temp_path" + if [[ -f $tool_temp_path ]]; then + rm -rf $tool_temp_path + fi + + exit 0 +fi + +# Install tool +if [[ -f $tool_file_path ]] && [[ $force = false ]]; then + echo "$tool_name ($version) already exists, skipping install" + exit 0 +fi + +DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds + +if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' + exit 1 +fi + +# Generate Shim +# Always rewrite shims so that we are referencing the expected version +NewScriptShim $shim_path $tool_file_path true + +if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' + exit 1 +fi + +exit 0 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/install-tool.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/install-tool.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/native/install-tool.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/native/install-tool.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,132 @@ +<# +.SYNOPSIS +Install native tool + +.DESCRIPTION +Install cmake native tool from Azure blob storage + +.PARAMETER InstallPath +Base directory to install native tool to + +.PARAMETER BaseUri +Base file directory or Url from which to acquire tool archives + +.PARAMETER CommonLibraryDirectory +Path to folder containing common library modules + +.PARAMETER Force +Force install of tools even if they previously exist + +.PARAMETER Clean +Don't install the tool, just clean up the current install of the tool + +.PARAMETER DownloadRetries +Total number of retry attempts + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds + +.NOTES +Returns 0 if install succeeds, 1 otherwise +#> +[CmdletBinding(PositionalBinding=$false)] +Param ( + [Parameter(Mandatory=$True)] + [string] $ToolName, + [Parameter(Mandatory=$True)] + [string] $InstallPath, + [Parameter(Mandatory=$True)] + [string] $BaseUri, + [Parameter(Mandatory=$True)] + [string] $Version, + [string] $CommonLibraryDirectory = $PSScriptRoot, + [switch] $Force = $False, + [switch] $Clean = $False, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30 +) + +. $PSScriptRoot\..\pipeline-logging-functions.ps1 + +# Import common library modules +Import-Module -Name (Join-Path $CommonLibraryDirectory "CommonLibrary.psm1") + +try { + # Define verbose switch if undefined + $Verbose = $VerbosePreference -Eq "Continue" + + $Arch = CommonLibrary\Get-MachineArchitecture + $ToolOs = "win64" + if($Arch -Eq "x32") { + $ToolOs = "win32" + } + $ToolNameMoniker = "$ToolName-$Version-$ToolOs-$Arch" + $ToolInstallDirectory = Join-Path $InstallPath "$ToolName\$Version\" + $Uri = "$BaseUri/windows/$ToolName/$ToolNameMoniker.zip" + $ShimPath = Join-Path $InstallPath "$ToolName.exe" + + if ($Clean) { + Write-Host "Cleaning $ToolInstallDirectory" + if (Test-Path $ToolInstallDirectory) { + Remove-Item $ToolInstallDirectory -Force -Recurse + } + Write-Host "Cleaning $ShimPath" + if (Test-Path $ShimPath) { + Remove-Item $ShimPath -Force + } + $ToolTempPath = CommonLibrary\Get-TempPathFilename -Path $Uri + Write-Host "Cleaning $ToolTempPath" + if (Test-Path $ToolTempPath) { + Remove-Item $ToolTempPath -Force + } + exit 0 + } + + # Install tool + if ((Test-Path $ToolInstallDirectory) -And (-Not $Force)) { + Write-Verbose "$ToolName ($Version) already exists, skipping install" + } + else { + $InstallStatus = CommonLibrary\DownloadAndExtract -Uri $Uri ` + -InstallDirectory $ToolInstallDirectory ` + -Force:$Force ` + -DownloadRetries $DownloadRetries ` + -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` + -Verbose:$Verbose + + if ($InstallStatus -Eq $False) { + Write-PipelineTelemetryError "Installation failed" -Category "NativeToolsetBootstrapping" + exit 1 + } + } + + $ToolFilePath = Get-ChildItem $ToolInstallDirectory -Recurse -Filter "$ToolName.exe" | % { $_.FullName } + if (@($ToolFilePath).Length -Gt 1) { + Write-Error "There are multiple copies of $ToolName in $($ToolInstallDirectory): `n$(@($ToolFilePath | out-string))" + exit 1 + } elseif (@($ToolFilePath).Length -Lt 1) { + Write-Host "$ToolName was not found in $ToolInstallDirectory." + exit 1 + } + + # Generate shim + # Always rewrite shims so that we are referencing the expected version + $GenerateShimStatus = CommonLibrary\New-ScriptShim -ShimName $ToolName ` + -ShimDirectory $InstallPath ` + -ToolFilePath "$ToolFilePath" ` + -BaseUri $BaseUri ` + -Force:$Force ` + -Verbose:$Verbose + + if ($GenerateShimStatus -Eq $False) { + Write-PipelineTelemetryError "Generate shim failed" -Category "NativeToolsetBootstrapping" + return 1 + } + + exit 0 +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category "NativeToolsetBootstrapping" -Message $_ + exit 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/pipeline-logging-functions.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/pipeline-logging-functions.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/pipeline-logging-functions.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/pipeline-logging-functions.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,260 @@ +# Source for this file was taken from https://github.com/microsoft/azure-pipelines-task-lib/blob/11c9439d4af17e6475d9fe058e6b2e03914d17e6/powershell/VstsTaskSdk/LoggingCommandFunctions.ps1 and modified. + +# NOTE: You should not be calling these method directly as they are likely to change. Instead you should be calling the Write-Pipeline* functions defined in tools.ps1 + +$script:loggingCommandPrefix = '##vso[' +$script:loggingCommandEscapeMappings = @( # TODO: WHAT ABOUT "="? WHAT ABOUT "%"? + New-Object psobject -Property @{ Token = ';' ; Replacement = '%3B' } + New-Object psobject -Property @{ Token = "`r" ; Replacement = '%0D' } + New-Object psobject -Property @{ Token = "`n" ; Replacement = '%0A' } + New-Object psobject -Property @{ Token = "]" ; Replacement = '%5D' } +) +# TODO: BUG: Escape % ??? +# TODO: Add test to verify don't need to escape "=". + +# Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set +function Write-PipelineTelemetryError { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Category, + [Parameter(Mandatory = $true)] + [string]$Message, + [Parameter(Mandatory = $false)] + [string]$Type = 'error', + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput, + [switch]$Force) + + $PSBoundParameters.Remove('Category') | Out-Null + + if ($Force -Or ((Test-Path variable:ci) -And $ci)) { + $Message = "(NETCORE_ENGINEERING_TELEMETRY=$Category) $Message" + } + $PSBoundParameters.Remove('Message') | Out-Null + $PSBoundParameters.Add('Message', $Message) + Write-PipelineTaskError @PSBoundParameters +} + +# Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set +function Write-PipelineTaskError { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Message, + [Parameter(Mandatory = $false)] + [string]$Type = 'error', + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput, + [switch]$Force + ) + + if (!$Force -And (-Not (Test-Path variable:ci) -Or !$ci)) { + if ($Type -eq 'error') { + Write-Host $Message -ForegroundColor Red + return + } + elseif ($Type -eq 'warning') { + Write-Host $Message -ForegroundColor Yellow + return + } + } + + if (($Type -ne 'error') -and ($Type -ne 'warning')) { + Write-Host $Message + return + } + $PSBoundParameters.Remove('Force') | Out-Null + if (-not $PSBoundParameters.ContainsKey('Type')) { + $PSBoundParameters.Add('Type', 'error') + } + Write-LogIssue @PSBoundParameters +} + +function Write-PipelineSetVariable { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Name, + [string]$Value, + [switch]$Secret, + [switch]$AsOutput, + [bool]$IsMultiJobVariable = $true) + + if ((Test-Path variable:ci) -And $ci) { + Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $Value -Properties @{ + 'variable' = $Name + 'isSecret' = $Secret + 'isOutput' = $IsMultiJobVariable + } -AsOutput:$AsOutput + } +} + +function Write-PipelinePrependPath { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [switch]$AsOutput) + + if ((Test-Path variable:ci) -And $ci) { + Write-LoggingCommand -Area 'task' -Event 'prependpath' -Data $Path -AsOutput:$AsOutput + } +} + +function Write-PipelineSetResult { + [CmdletBinding()] + param( + [ValidateSet("Succeeded", "SucceededWithIssues", "Failed", "Cancelled", "Skipped")] + [Parameter(Mandatory = $true)] + [string]$Result, + [string]$Message) + if ((Test-Path variable:ci) -And $ci) { + Write-LoggingCommand -Area 'task' -Event 'complete' -Data $Message -Properties @{ + 'result' = $Result + } + } +} + +<######################################## +# Private functions. +########################################> +function Format-LoggingCommandData { + [CmdletBinding()] + param([string]$Value, [switch]$Reverse) + + if (!$Value) { + return '' + } + + if (!$Reverse) { + foreach ($mapping in $script:loggingCommandEscapeMappings) { + $Value = $Value.Replace($mapping.Token, $mapping.Replacement) + } + } + else { + for ($i = $script:loggingCommandEscapeMappings.Length - 1 ; $i -ge 0 ; $i--) { + $mapping = $script:loggingCommandEscapeMappings[$i] + $Value = $Value.Replace($mapping.Replacement, $mapping.Token) + } + } + + return $Value +} + +function Format-LoggingCommand { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Area, + [Parameter(Mandatory = $true)] + [string]$Event, + [string]$Data, + [hashtable]$Properties) + + # Append the preamble. + [System.Text.StringBuilder]$sb = New-Object -TypeName System.Text.StringBuilder + $null = $sb.Append($script:loggingCommandPrefix).Append($Area).Append('.').Append($Event) + + # Append the properties. + if ($Properties) { + $first = $true + foreach ($key in $Properties.Keys) { + [string]$value = Format-LoggingCommandData $Properties[$key] + if ($value) { + if ($first) { + $null = $sb.Append(' ') + $first = $false + } + else { + $null = $sb.Append(';') + } + + $null = $sb.Append("$key=$value") + } + } + } + + # Append the tail and output the value. + $Data = Format-LoggingCommandData $Data + $sb.Append(']').Append($Data).ToString() +} + +function Write-LoggingCommand { + [CmdletBinding(DefaultParameterSetName = 'Parameters')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] + [string]$Area, + [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] + [string]$Event, + [Parameter(ParameterSetName = 'Parameters')] + [string]$Data, + [Parameter(ParameterSetName = 'Parameters')] + [hashtable]$Properties, + [Parameter(Mandatory = $true, ParameterSetName = 'Object')] + $Command, + [switch]$AsOutput) + + if ($PSCmdlet.ParameterSetName -eq 'Object') { + Write-LoggingCommand -Area $Command.Area -Event $Command.Event -Data $Command.Data -Properties $Command.Properties -AsOutput:$AsOutput + return + } + + $command = Format-LoggingCommand -Area $Area -Event $Event -Data $Data -Properties $Properties + if ($AsOutput) { + $command + } + else { + Write-Host $command + } +} + +function Write-LogIssue { + [CmdletBinding()] + param( + [ValidateSet('warning', 'error')] + [Parameter(Mandatory = $true)] + [string]$Type, + [string]$Message, + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput) + + $command = Format-LoggingCommand -Area 'task' -Event 'logissue' -Data $Message -Properties @{ + 'type' = $Type + 'code' = $ErrCode + 'sourcepath' = $SourcePath + 'linenumber' = $LineNumber + 'columnnumber' = $ColumnNumber + } + if ($AsOutput) { + return $command + } + + if ($Type -eq 'error') { + $foregroundColor = $host.PrivateData.ErrorForegroundColor + $backgroundColor = $host.PrivateData.ErrorBackgroundColor + if ($foregroundColor -isnot [System.ConsoleColor] -or $backgroundColor -isnot [System.ConsoleColor]) { + $foregroundColor = [System.ConsoleColor]::Red + $backgroundColor = [System.ConsoleColor]::Black + } + } + else { + $foregroundColor = $host.PrivateData.WarningForegroundColor + $backgroundColor = $host.PrivateData.WarningBackgroundColor + if ($foregroundColor -isnot [System.ConsoleColor] -or $backgroundColor -isnot [System.ConsoleColor]) { + $foregroundColor = [System.ConsoleColor]::Yellow + $backgroundColor = [System.ConsoleColor]::Black + } + } + + Write-Host $command -ForegroundColor $foregroundColor -BackgroundColor $backgroundColor +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/pipeline-logging-functions.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/pipeline-logging-functions.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/pipeline-logging-functions.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/pipeline-logging-functions.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,206 @@ +#!/usr/bin/env bash + +function Write-PipelineTelemetryError { + local telemetry_category='' + local force=false + local function_args=() + local message='' + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -category|-c) + telemetry_category=$2 + shift + ;; + -force|-f) + force=true + ;; + -*) + function_args+=("$1 $2") + shift + ;; + *) + message=$* + ;; + esac + shift + done + + if [[ $force != true ]] && [[ "$ci" != true ]]; then + echo "$message" >&2 + return + fi + + if [[ $force == true ]]; then + function_args+=("-force") + fi + message="(NETCORE_ENGINEERING_TELEMETRY=$telemetry_category) $message" + function_args+=("$message") + Write-PipelineTaskError ${function_args[@]} +} + +function Write-PipelineTaskError { + local message_type="error" + local sourcepath='' + local linenumber='' + local columnnumber='' + local error_code='' + local force=false + + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -type|-t) + message_type=$2 + shift + ;; + -sourcepath|-s) + sourcepath=$2 + shift + ;; + -linenumber|-ln) + linenumber=$2 + shift + ;; + -columnnumber|-cn) + columnnumber=$2 + shift + ;; + -errcode|-e) + error_code=$2 + shift + ;; + -force|-f) + force=true + ;; + *) + break + ;; + esac + + shift + done + + if [[ $force != true ]] && [[ "$ci" != true ]]; then + echo "$@" >&2 + return + fi + + local message="##vso[task.logissue" + + message="$message type=$message_type" + + if [ -n "$sourcepath" ]; then + message="$message;sourcepath=$sourcepath" + fi + + if [ -n "$linenumber" ]; then + message="$message;linenumber=$linenumber" + fi + + if [ -n "$columnnumber" ]; then + message="$message;columnnumber=$columnnumber" + fi + + if [ -n "$error_code" ]; then + message="$message;code=$error_code" + fi + + message="$message]$*" + echo "$message" +} + +function Write-PipelineSetVariable { + if [[ "$ci" != true ]]; then + return + fi + + local name='' + local value='' + local secret=false + local as_output=false + local is_multi_job_variable=true + + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -name|-n) + name=$2 + shift + ;; + -value|-v) + value=$2 + shift + ;; + -secret|-s) + secret=true + ;; + -as_output|-a) + as_output=true + ;; + -is_multi_job_variable|-i) + is_multi_job_variable=$2 + shift + ;; + esac + shift + done + + value=${value/;/%3B} + value=${value/\\r/%0D} + value=${value/\\n/%0A} + value=${value/]/%5D} + + local message="##vso[task.setvariable variable=$name;isSecret=$secret;isOutput=$is_multi_job_variable]$value" + + if [[ "$as_output" == true ]]; then + $message + else + echo "$message" + fi +} + +function Write-PipelinePrependPath { + local prepend_path='' + + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -path|-p) + prepend_path=$2 + shift + ;; + esac + shift + done + + export PATH="$prepend_path:$PATH" + + if [[ "$ci" == true ]]; then + echo "##vso[task.prependpath]$prepend_path" + fi +} + +function Write-PipelineSetResult { + local result='' + local message='' + + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -result|-r) + result=$2 + shift + ;; + -message|-m) + message=$2 + shift + ;; + esac + shift + done + + if [[ "$ci" == true ]]; then + echo "##vso[task.complete result=$result;]$message" + fi +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/add-build-to-channel.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/add-build-to-channel.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/add-build-to-channel.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/add-build-to-channel.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,48 @@ +param( + [Parameter(Mandatory=$true)][int] $BuildId, + [Parameter(Mandatory=$true)][int] $ChannelId, + [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', + [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + # Check that the channel we are going to promote the build to exist + $channelInfo = Get-MaestroChannel -ChannelId $ChannelId + + if (!$channelInfo) { + Write-PipelineTelemetryCategory -Category 'PromoteBuild' -Message "Channel with BAR ID $ChannelId was not found in BAR!" + ExitWithExitCode 1 + } + + # Get info about which channel(s) the build has already been promoted to + $buildInfo = Get-MaestroBuild -BuildId $BuildId + + if (!$buildInfo) { + Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "Build with BAR ID $BuildId was not found in BAR!" + ExitWithExitCode 1 + } + + # Find whether the build is already assigned to the channel or not + if ($buildInfo.channels) { + foreach ($channel in $buildInfo.channels) { + if ($channel.Id -eq $ChannelId) { + Write-Host "The build with BAR ID $BuildId is already on channel $ChannelId!" + ExitWithExitCode 0 + } + } + } + + Write-Host "Promoting build '$BuildId' to channel '$ChannelId'." + + Assign-BuildToChannel -BuildId $BuildId -ChannelId $ChannelId + + Write-Host 'done.' +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to promote build '$BuildId' to channel '$ChannelId'" + ExitWithExitCode 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/check-channel-consistency.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/check-channel-consistency.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/check-channel-consistency.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/check-channel-consistency.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,40 @@ +param( + [Parameter(Mandatory=$true)][string] $PromoteToChannels, # List of channels that the build should be promoted to + [Parameter(Mandatory=$true)][array] $AvailableChannelIds # List of channel IDs available in the YAML implementation +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + if ($PromoteToChannels -eq "") { + Write-PipelineTaskError -Type 'warning' -Message "This build won't publish assets as it's not configured to any Maestro channel. If that wasn't intended use Darc to configure a default channel using add-default-channel for this branch or to promote it to a channel using add-build-to-channel. See https://github.com/dotnet/arcade/blob/master/Documentation/Darc.md#assigning-an-individual-build-to-a-channel for more info." + ExitWithExitCode 0 + } + + # Check that every channel that Maestro told to promote the build to + # is available in YAML + $PromoteToChannelsIds = $PromoteToChannels -split "\D" | Where-Object { $_ } + + $hasErrors = $false + + foreach ($id in $PromoteToChannelsIds) { + if (($id -ne 0) -and ($id -notin $AvailableChannelIds)) { + Write-PipelineTaskError -Message "Channel $id is not present in the post-build YAML configuration! This is an error scenario. Please contact @dnceng." + $hasErrors = $true + } + } + + # The `Write-PipelineTaskError` doesn't error the script and we might report several errors + # in the previous lines. The check below makes sure that we return an error state from the + # script if we reported any validation error + if ($hasErrors) { + ExitWithExitCode 1 + } + + Write-Host 'done.' +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Category 'CheckChannelConsistency' -Message "There was an error while trying to check consistency of Maestro default channels for the build and post-build YAML configuration." + ExitWithExitCode 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/nuget-validation.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/nuget-validation.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/nuget-validation.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/nuget-validation.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,24 @@ +# This script validates NuGet package metadata information using this +# tool: https://github.com/NuGet/NuGetGallery/tree/jver-verify/src/VerifyMicrosoftPackage + +param( + [Parameter(Mandatory=$true)][string] $PackagesPath, # Path to where the packages to be validated are + [Parameter(Mandatory=$true)][string] $ToolDestinationPath # Where the validation tool should be downloaded to +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + $url = 'https://raw.githubusercontent.com/NuGet/NuGetGallery/3e25ad135146676bcab0050a516939d9958bfa5d/src/VerifyMicrosoftPackage/verify.ps1' + + New-Item -ItemType 'directory' -Path ${ToolDestinationPath} -Force + + Invoke-WebRequest $url -OutFile ${ToolDestinationPath}\verify.ps1 + + & ${ToolDestinationPath}\verify.ps1 ${PackagesPath}\*.nupkg +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'NuGetValidation' -Message $_ + ExitWithExitCode 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/post-build-utils.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/post-build-utils.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/post-build-utils.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/post-build-utils.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,91 @@ +# Most of the functions in this file require the variables `MaestroApiEndPoint`, +# `MaestroApiVersion` and `MaestroApiAccessToken` to be globally available. + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +# `tools.ps1` checks $ci to perform some actions. Since the post-build +# scripts don't necessarily execute in the same agent that run the +# build.ps1/sh script this variable isn't automatically set. +$ci = $true +$disableConfigureToolsetImport = $true +. $PSScriptRoot\..\tools.ps1 + +function Create-MaestroApiRequestHeaders([string]$ContentType = 'application/json') { + Validate-MaestroVars + + $headers = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' + $headers.Add('Accept', $ContentType) + $headers.Add('Authorization',"Bearer $MaestroApiAccessToken") + return $headers +} + +function Get-MaestroChannel([int]$ChannelId) { + Validate-MaestroVars + + $apiHeaders = Create-MaestroApiRequestHeaders + $apiEndpoint = "$MaestroApiEndPoint/api/channels/${ChannelId}?api-version=$MaestroApiVersion" + + $result = try { Invoke-WebRequest -Method Get -Uri $apiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + return $result +} + +function Get-MaestroBuild([int]$BuildId) { + Validate-MaestroVars + + $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken + $apiEndpoint = "$MaestroApiEndPoint/api/builds/${BuildId}?api-version=$MaestroApiVersion" + + $result = try { return Invoke-WebRequest -Method Get -Uri $apiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + return $result +} + +function Get-MaestroSubscriptions([string]$SourceRepository, [int]$ChannelId) { + Validate-MaestroVars + + $SourceRepository = [System.Web.HttpUtility]::UrlEncode($SourceRepository) + $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken + $apiEndpoint = "$MaestroApiEndPoint/api/subscriptions?sourceRepository=$SourceRepository&channelId=$ChannelId&api-version=$MaestroApiVersion" + + $result = try { Invoke-WebRequest -Method Get -Uri $apiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + return $result +} + +function Assign-BuildToChannel([int]$BuildId, [int]$ChannelId) { + Validate-MaestroVars + + $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken + $apiEndpoint = "$MaestroApiEndPoint/api/channels/${ChannelId}/builds/${BuildId}?api-version=$MaestroApiVersion" + Invoke-WebRequest -Method Post -Uri $apiEndpoint -Headers $apiHeaders | Out-Null +} + +function Trigger-Subscription([string]$SubscriptionId) { + Validate-MaestroVars + + $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken + $apiEndpoint = "$MaestroApiEndPoint/api/subscriptions/$SubscriptionId/trigger?api-version=$MaestroApiVersion" + Invoke-WebRequest -Uri $apiEndpoint -Headers $apiHeaders -Method Post | Out-Null +} + +function Validate-MaestroVars { + try { + Get-Variable MaestroApiEndPoint | Out-Null + Get-Variable MaestroApiVersion | Out-Null + Get-Variable MaestroApiAccessToken | Out-Null + + if (!($MaestroApiEndPoint -Match '^http[s]?://maestro-(int|prod).westus2.cloudapp.azure.com$')) { + Write-PipelineTelemetryError -Category 'MaestroVars' -Message "MaestroApiEndPoint is not a valid Maestro URL. '$MaestroApiEndPoint'" + ExitWithExitCode 1 + } + + if (!($MaestroApiVersion -Match '^[0-9]{4}-[0-9]{2}-[0-9]{2}$')) { + Write-PipelineTelemetryError -Category 'MaestroVars' -Message "MaestroApiVersion does not match a version string in the format yyyy-MM-DD. '$MaestroApiVersion'" + ExitWithExitCode 1 + } + } + catch { + Write-PipelineTelemetryError -Category 'MaestroVars' -Message 'Error: Variables `MaestroApiEndPoint`, `MaestroApiVersion` and `MaestroApiAccessToken` are required while using this script.' + Write-Host $_ + ExitWithExitCode 1 + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/publish-using-darc.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/publish-using-darc.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/publish-using-darc.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/publish-using-darc.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,54 @@ +param( + [Parameter(Mandatory=$true)][int] $BuildId, + [Parameter(Mandatory=$true)][int] $PublishingInfraVersion, + [Parameter(Mandatory=$true)][string] $AzdoToken, + [Parameter(Mandatory=$true)][string] $MaestroToken, + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', + [Parameter(Mandatory=$true)][string] $WaitPublishingFinish, + [Parameter(Mandatory=$false)][string] $ArtifactsPublishingAdditionalParameters, + [Parameter(Mandatory=$false)][string] $SymbolPublishingAdditionalParameters +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + $darc = Get-Darc + + $optionalParams = [System.Collections.ArrayList]::new() + + if ("" -ne $ArtifactsPublishingAdditionalParameters) { + $optionalParams.Add("--artifact-publishing-parameters") | Out-Null + $optionalParams.Add($ArtifactsPublishingAdditionalParameters) | Out-Null + } + + if ("" -ne $SymbolPublishingAdditionalParameters) { + $optionalParams.Add("--symbol-publishing-parameters") | Out-Null + $optionalParams.Add($SymbolPublishingAdditionalParameters) | Out-Null + } + + if ("false" -eq $WaitPublishingFinish) { + $optionalParams.Add("--no-wait") | Out-Null + } + + & $darc add-build-to-channel ` + --id $buildId ` + --publishing-infra-version $PublishingInfraVersion ` + --default-channels ` + --source-branch main ` + --azdev-pat $AzdoToken ` + --bar-uri $MaestroApiEndPoint ` + --password $MaestroToken ` + @optionalParams + + if ($LastExitCode -ne 0) { + Write-Host "Problems using Darc to promote build ${buildId} to default channels. Stopping execution..." + exit 1 + } + + Write-Host 'done.' +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to publish build '$BuildId' to default channels." + ExitWithExitCode 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/sourcelink-validation.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/sourcelink-validation.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/sourcelink-validation.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/sourcelink-validation.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,319 @@ +param( + [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where Symbols.NuGet packages to be checked are stored + [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation + [Parameter(Mandatory=$false)][string] $GHRepoName, # GitHub name of the repo including the Org. E.g., dotnet/arcade + [Parameter(Mandatory=$false)][string] $GHCommit, # GitHub commit SHA used to build the packages + [Parameter(Mandatory=$true)][string] $SourcelinkCliVersion # Version of SourceLink CLI to use +) + +. $PSScriptRoot\post-build-utils.ps1 + +# Cache/HashMap (File -> Exist flag) used to consult whether a file exist +# in the repository at a specific commit point. This is populated by inserting +# all files present in the repo at a specific commit point. +$global:RepoFiles = @{} + +# Maximum number of jobs to run in parallel +$MaxParallelJobs = 16 + +$MaxRetries = 5 +$RetryWaitTimeInSeconds = 30 + +# Wait time between check for system load +$SecondsBetweenLoadChecks = 10 + +if (!$InputPath -or !(Test-Path $InputPath)){ + Write-Host "No files to validate." + ExitWithExitCode 0 +} + +$ValidatePackage = { + param( + [string] $PackagePath # Full path to a Symbols.NuGet package + ) + + . $using:PSScriptRoot\..\tools.ps1 + + # Ensure input file exist + if (!(Test-Path $PackagePath)) { + Write-Host "Input file does not exist: $PackagePath" + return [pscustomobject]@{ + result = 1 + packagePath = $PackagePath + } + } + + # Extensions for which we'll look for SourceLink information + # For now we'll only care about Portable & Embedded PDBs + $RelevantExtensions = @('.dll', '.exe', '.pdb') + + Write-Host -NoNewLine 'Validating ' ([System.IO.Path]::GetFileName($PackagePath)) '...' + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId + $FailedFiles = 0 + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + [System.IO.Directory]::CreateDirectory($ExtractPath) | Out-Null + + try { + $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) + + $zip.Entries | + Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | + ForEach-Object { + $FileName = $_.FullName + $Extension = [System.IO.Path]::GetExtension($_.Name) + $FakeName = -Join((New-Guid), $Extension) + $TargetFile = Join-Path -Path $ExtractPath -ChildPath $FakeName + + # We ignore resource DLLs + if ($FileName.EndsWith('.resources.dll')) { + return [pscustomobject]@{ + result = 0 + packagePath = $PackagePath + } + } + + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) + + $ValidateFile = { + param( + [string] $FullPath, # Full path to the module that has to be checked + [string] $RealPath, + [ref] $FailedFiles + ) + + $sourcelinkExe = "$env:USERPROFILE\.dotnet\tools" + $sourcelinkExe = Resolve-Path "$sourcelinkExe\sourcelink.exe" + $SourceLinkInfos = & $sourcelinkExe print-urls $FullPath | Out-String + + if ($LASTEXITCODE -eq 0 -and -not ([string]::IsNullOrEmpty($SourceLinkInfos))) { + $NumFailedLinks = 0 + + # We only care about Http addresses + $Matches = (Select-String '(http[s]?)(:\/\/)([^\s,]+)' -Input $SourceLinkInfos -AllMatches).Matches + + if ($Matches.Count -ne 0) { + $Matches.Value | + ForEach-Object { + $Link = $_ + $CommitUrl = "https://raw.githubusercontent.com/${using:GHRepoName}/${using:GHCommit}/" + + $FilePath = $Link.Replace($CommitUrl, "") + $Status = 200 + $Cache = $using:RepoFiles + + $attempts = 0 + + while ($attempts -lt $using:MaxRetries) { + if ( !($Cache.ContainsKey($FilePath)) ) { + try { + $Uri = $Link -as [System.URI] + + if ($Link -match "submodules") { + # Skip submodule links until sourcelink properly handles submodules + $Status = 200 + } + elseif ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match 'github' -or $Uri.Host -match 'githubusercontent')) { + # Only GitHub links are valid + $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode + } + else { + # If it's not a github link, we want to break out of the loop and not retry. + $Status = 0 + $attempts = $using:MaxRetries + } + } + catch { + Write-Host $_ + $Status = 0 + } + } + + if ($Status -ne 200) { + $attempts++ + + if ($attempts -lt $using:MaxRetries) + { + $attemptsLeft = $using:MaxRetries - $attempts + Write-Warning "Download failed, $attemptsLeft attempts remaining, will retry in $using:RetryWaitTimeInSeconds seconds" + Start-Sleep -Seconds $using:RetryWaitTimeInSeconds + } + else { + if ($NumFailedLinks -eq 0) { + if ($FailedFiles.Value -eq 0) { + Write-Host + } + + Write-Host "`tFile $RealPath has broken links:" + } + + Write-Host "`t`tFailed to retrieve $Link" + + $NumFailedLinks++ + } + } + else { + break + } + } + } + } + + if ($NumFailedLinks -ne 0) { + $FailedFiles.value++ + $global:LASTEXITCODE = 1 + } + } + } + + &$ValidateFile $TargetFile $FileName ([ref]$FailedFiles) + } + } + catch { + Write-Host $_ + } + finally { + $zip.Dispose() + } + + if ($FailedFiles -eq 0) { + Write-Host 'Passed.' + return [pscustomobject]@{ + result = 0 + packagePath = $PackagePath + } + } + else { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "$PackagePath has broken SourceLink links." + return [pscustomobject]@{ + result = 1 + packagePath = $PackagePath + } + } +} + +function CheckJobResult( + $result, + $packagePath, + [ref]$ValidationFailures, + [switch]$logErrors) { + if ($result -ne '0') { + if ($logErrors) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "$packagePath has broken SourceLink links." + } + $ValidationFailures.Value++ + } +} + +function ValidateSourceLinkLinks { + if ($GHRepoName -ne '' -and !($GHRepoName -Match '^[^\s\/]+/[^\s\/]+$')) { + if (!($GHRepoName -Match '^[^\s-]+-[^\s]+$')) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHRepoName should be in the format / or -. '$GHRepoName'" + ExitWithExitCode 1 + } + else { + $GHRepoName = $GHRepoName -replace '^([^\s-]+)-([^\s]+)$', '$1/$2'; + } + } + + if ($GHCommit -ne '' -and !($GHCommit -Match '^[0-9a-fA-F]{40}$')) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHCommit should be a 40 chars hexadecimal string. '$GHCommit'" + ExitWithExitCode 1 + } + + if ($GHRepoName -ne '' -and $GHCommit -ne '') { + $RepoTreeURL = -Join('http://api.github.com/repos/', $GHRepoName, '/git/trees/', $GHCommit, '?recursive=1') + $CodeExtensions = @('.cs', '.vb', '.fs', '.fsi', '.fsx', '.fsscript') + + try { + # Retrieve the list of files in the repo at that particular commit point and store them in the RepoFiles hash + $Data = Invoke-WebRequest $RepoTreeURL -UseBasicParsing | ConvertFrom-Json | Select-Object -ExpandProperty tree + + foreach ($file in $Data) { + $Extension = [System.IO.Path]::GetExtension($file.path) + + if ($CodeExtensions.Contains($Extension)) { + $RepoFiles[$file.path] = 1 + } + } + } + catch { + Write-Host "Problems downloading the list of files from the repo. Url used: $RepoTreeURL . Execution will proceed without caching." + } + } + elseif ($GHRepoName -ne '' -or $GHCommit -ne '') { + Write-Host 'For using the http caching mechanism both GHRepoName and GHCommit should be informed.' + } + + if (Test-Path $ExtractPath) { + Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue + } + + $ValidationFailures = 0 + + # Process each NuGet package in parallel + Get-ChildItem "$InputPath\*.symbols.nupkg" | + ForEach-Object { + Write-Host "Starting $($_.FullName)" + Start-Job -ScriptBlock $ValidatePackage -ArgumentList $_.FullName | Out-Null + $NumJobs = @(Get-Job -State 'Running').Count + + while ($NumJobs -ge $MaxParallelJobs) { + Write-Host "There are $NumJobs validation jobs running right now. Waiting $SecondsBetweenLoadChecks seconds to check again." + sleep $SecondsBetweenLoadChecks + $NumJobs = @(Get-Job -State 'Running').Count + } + + foreach ($Job in @(Get-Job -State 'Completed')) { + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$ValidationFailures) -LogErrors + Remove-Job -Id $Job.Id + } + } + + foreach ($Job in @(Get-Job)) { + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$ValidationFailures) + Remove-Job -Id $Job.Id + } + if ($ValidationFailures -gt 0) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "$ValidationFailures package(s) failed validation." + ExitWithExitCode 1 + } +} + +function InstallSourcelinkCli { + $sourcelinkCliPackageName = 'sourcelink' + + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + $toolList = & "$dotnet" tool list --global + + if (($toolList -like "*$sourcelinkCliPackageName*") -and ($toolList -like "*$sourcelinkCliVersion*")) { + Write-Host "SourceLink CLI version $sourcelinkCliVersion is already installed." + } + else { + Write-Host "Installing SourceLink CLI version $sourcelinkCliVersion..." + Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' + & "$dotnet" tool install $sourcelinkCliPackageName --version $sourcelinkCliVersion --verbosity "minimal" --global + } +} + +try { + InstallSourcelinkCli + + foreach ($Job in @(Get-Job)) { + Remove-Job -Id $Job.Id + } + + ValidateSourceLinkLinks +} +catch { + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'SourceLink' -Message $_ + ExitWithExitCode 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/symbols-validation.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/symbols-validation.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/symbols-validation.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/symbols-validation.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,339 @@ +param( + [Parameter(Mandatory = $true)][string] $InputPath, # Full path to directory where NuGet packages to be checked are stored + [Parameter(Mandatory = $true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation + [Parameter(Mandatory = $true)][string] $DotnetSymbolVersion, # Version of dotnet symbol to use + [Parameter(Mandatory = $false)][switch] $CheckForWindowsPdbs, # If we should check for the existence of windows pdbs in addition to portable PDBs + [Parameter(Mandatory = $false)][switch] $ContinueOnError, # If we should keep checking symbols after an error + [Parameter(Mandatory = $false)][switch] $Clean, # Clean extracted symbols directory after checking symbols + [Parameter(Mandatory = $false)][string] $SymbolExclusionFile # Exclude the symbols in the file from publishing to symbol server +) + +. $PSScriptRoot\..\tools.ps1 +# Maximum number of jobs to run in parallel +$MaxParallelJobs = 16 + +# Max number of retries +$MaxRetry = 5 + +# Wait time between check for system load +$SecondsBetweenLoadChecks = 10 + +# Set error codes +Set-Variable -Name "ERROR_BADEXTRACT" -Option Constant -Value -1 +Set-Variable -Name "ERROR_FILEDOESNOTEXIST" -Option Constant -Value -2 + +$WindowsPdbVerificationParam = "" +if ($CheckForWindowsPdbs) { + $WindowsPdbVerificationParam = "--windows-pdbs" +} + +$ExclusionSet = New-Object System.Collections.Generic.HashSet[string]; + +if (!$InputPath -or !(Test-Path $InputPath)){ + Write-Host "No symbols to validate." + ExitWithExitCode 0 +} + +#Check if the path exists +if ($SymbolExclusionFile -and (Test-Path $SymbolExclusionFile)){ + [string[]]$Exclusions = Get-Content "$SymbolExclusionFile" + $Exclusions | foreach { if($_ -and $_.Trim()){$ExclusionSet.Add($_)} } +} +else{ + Write-Host "Symbol Exclusion file does not exists. No symbols to exclude." +} + +$CountMissingSymbols = { + param( + [string] $PackagePath, # Path to a NuGet package + [string] $WindowsPdbVerificationParam # If we should check for the existence of windows pdbs in addition to portable PDBs + ) + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + Write-Host "Validating $PackagePath " + + # Ensure input file exist + if (!(Test-Path $PackagePath)) { + Write-PipelineTaskError "Input file does not exist: $PackagePath" + return [pscustomobject]@{ + result = $using:ERROR_FILEDOESNOTEXIST + packagePath = $PackagePath + } + } + + # Extensions for which we'll look for symbols + $RelevantExtensions = @('.dll', '.exe', '.so', '.dylib') + + # How many files are missing symbol information + $MissingSymbols = 0 + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $PackageGuid = New-Guid + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageGuid + $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath 'Symbols' + + try { + [System.IO.Compression.ZipFile]::ExtractToDirectory($PackagePath, $ExtractPath) + } + catch { + Write-Host "Something went wrong extracting $PackagePath" + Write-Host $_ + return [pscustomobject]@{ + result = $using:ERROR_BADEXTRACT + packagePath = $PackagePath + } + } + + Get-ChildItem -Recurse $ExtractPath | + Where-Object { $RelevantExtensions -contains $_.Extension } | + ForEach-Object { + $FileName = $_.FullName + if ($FileName -Match '\\ref\\') { + Write-Host "`t Ignoring reference assembly file " $FileName + return + } + + $FirstMatchingSymbolDescriptionOrDefault = { + param( + [string] $FullPath, # Full path to the module that has to be checked + [string] $TargetServerParam, # Parameter to pass to `Symbol Tool` indicating the server to lookup for symbols + [string] $WindowsPdbVerificationParam, # Parameter to pass to potential check for windows-pdbs. + [string] $SymbolsPath + ) + + $FileName = [System.IO.Path]::GetFileName($FullPath) + $Extension = [System.IO.Path]::GetExtension($FullPath) + + # Those below are potential symbol files that the `dotnet symbol` might + # return. Which one will be returned depend on the type of file we are + # checking and which type of file was uploaded. + + # The file itself is returned + $SymbolPath = $SymbolsPath + '\' + $FileName + + # PDB file for the module + $PdbPath = $SymbolPath.Replace($Extension, '.pdb') + + # PDB file for R2R module (created by crossgen) + $NGenPdb = $SymbolPath.Replace($Extension, '.ni.pdb') + + # DBG file for a .so library + $SODbg = $SymbolPath.Replace($Extension, '.so.dbg') + + # DWARF file for a .dylib + $DylibDwarf = $SymbolPath.Replace($Extension, '.dylib.dwarf') + + $dotnetSymbolExe = "$env:USERPROFILE\.dotnet\tools" + $dotnetSymbolExe = Resolve-Path "$dotnetSymbolExe\dotnet-symbol.exe" + + $totalRetries = 0 + + while ($totalRetries -lt $using:MaxRetry) { + + # Save the output and get diagnostic output + $output = & $dotnetSymbolExe --symbols --modules $WindowsPdbVerificationParam $TargetServerParam $FullPath -o $SymbolsPath --diagnostics | Out-String + + if ((Test-Path $PdbPath) -and (Test-path $SymbolPath)) { + return 'Module and PDB for Module' + } + elseif ((Test-Path $NGenPdb) -and (Test-Path $PdbPath) -and (Test-Path $SymbolPath)) { + return 'Dll, PDB and NGen PDB' + } + elseif ((Test-Path $SODbg) -and (Test-Path $SymbolPath)) { + return 'So and DBG for SO' + } + elseif ((Test-Path $DylibDwarf) -and (Test-Path $SymbolPath)) { + return 'Dylib and Dwarf for Dylib' + } + elseif (Test-Path $SymbolPath) { + return 'Module' + } + else + { + $totalRetries++ + } + } + + return $null + } + + $FileRelativePath = $FileName.Replace("$ExtractPath\", "") + if (($($using:ExclusionSet) -ne $null) -and ($($using:ExclusionSet).Contains($FileRelativePath) -or ($($using:ExclusionSet).Contains($FileRelativePath.Replace("\", "/"))))){ + Write-Host "Skipping $FileName from symbol validation" + } + + else { + $FileGuid = New-Guid + $ExpandedSymbolsPath = Join-Path -Path $SymbolsPath -ChildPath $FileGuid + + $SymbolsOnMSDL = & $FirstMatchingSymbolDescriptionOrDefault ` + -FullPath $FileName ` + -TargetServerParam '--microsoft-symbol-server' ` + -SymbolsPath "$ExpandedSymbolsPath-msdl" ` + -WindowsPdbVerificationParam $WindowsPdbVerificationParam + $SymbolsOnSymWeb = & $FirstMatchingSymbolDescriptionOrDefault ` + -FullPath $FileName ` + -TargetServerParam '--internal-server' ` + -SymbolsPath "$ExpandedSymbolsPath-symweb" ` + -WindowsPdbVerificationParam $WindowsPdbVerificationParam + + Write-Host -NoNewLine "`t Checking file " $FileName "... " + + if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) { + Write-Host "Symbols found on MSDL ($SymbolsOnMSDL) and SymWeb ($SymbolsOnSymWeb)" + } + else { + $MissingSymbols++ + + if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) { + Write-Host 'No symbols found on MSDL or SymWeb!' + } + else { + if ($SymbolsOnMSDL -eq $null) { + Write-Host 'No symbols found on MSDL!' + } + else { + Write-Host 'No symbols found on SymWeb!' + } + } + } + } + } + + if ($using:Clean) { + Remove-Item $ExtractPath -Recurse -Force + } + + Pop-Location + + return [pscustomobject]@{ + result = $MissingSymbols + packagePath = $PackagePath + } +} + +function CheckJobResult( + $result, + $packagePath, + [ref]$DupedSymbols, + [ref]$TotalFailures) { + if ($result -eq $ERROR_BADEXTRACT) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$packagePath has duplicated symbol files" + $DupedSymbols.Value++ + } + elseif ($result -eq $ERROR_FILEDOESNOTEXIST) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$packagePath does not exist" + $TotalFailures.Value++ + } + elseif ($result -gt '0') { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Missing symbols for $result modules in the package $packagePath" + $TotalFailures.Value++ + } + else { + Write-Host "All symbols verified for package $packagePath" + } +} + +function CheckSymbolsAvailable { + if (Test-Path $ExtractPath) { + Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue + } + + $TotalPackages = 0 + $TotalFailures = 0 + $DupedSymbols = 0 + + Get-ChildItem "$InputPath\*.nupkg" | + ForEach-Object { + $FileName = $_.Name + $FullName = $_.FullName + + # These packages from Arcade-Services include some native libraries that + # our current symbol uploader can't handle. Below is a workaround until + # we get issue: https://github.com/dotnet/arcade/issues/2457 sorted. + if ($FileName -Match 'Microsoft\.DotNet\.Darc\.') { + Write-Host "Ignoring Arcade-services file: $FileName" + Write-Host + return + } + elseif ($FileName -Match 'Microsoft\.DotNet\.Maestro\.Tasks\.') { + Write-Host "Ignoring Arcade-services file: $FileName" + Write-Host + return + } + + $TotalPackages++ + + Start-Job -ScriptBlock $CountMissingSymbols -ArgumentList @($FullName,$WindowsPdbVerificationParam) | Out-Null + + $NumJobs = @(Get-Job -State 'Running').Count + + while ($NumJobs -ge $MaxParallelJobs) { + Write-Host "There are $NumJobs validation jobs running right now. Waiting $SecondsBetweenLoadChecks seconds to check again." + sleep $SecondsBetweenLoadChecks + $NumJobs = @(Get-Job -State 'Running').Count + } + + foreach ($Job in @(Get-Job -State 'Completed')) { + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$DupedSymbols) ([ref]$TotalFailures) + Remove-Job -Id $Job.Id + } + Write-Host + } + + foreach ($Job in @(Get-Job)) { + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$DupedSymbols) ([ref]$TotalFailures) + } + + if ($TotalFailures -gt 0 -or $DupedSymbols -gt 0) { + if ($TotalFailures -gt 0) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Symbols missing for $TotalFailures/$TotalPackages packages" + } + + if ($DupedSymbols -gt 0) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$DupedSymbols/$TotalPackages packages had duplicated symbol files and could not be extracted" + } + + ExitWithExitCode 1 + } + else { + Write-Host "All symbols validated!" + } +} + +function InstallDotnetSymbol { + $dotnetSymbolPackageName = 'dotnet-symbol' + + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + $toolList = & "$dotnet" tool list --global + + if (($toolList -like "*$dotnetSymbolPackageName*") -and ($toolList -like "*$dotnetSymbolVersion*")) { + Write-Host "dotnet-symbol version $dotnetSymbolVersion is already installed." + } + else { + Write-Host "Installing dotnet-symbol version $dotnetSymbolVersion..." + Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' + & "$dotnet" tool install $dotnetSymbolPackageName --version $dotnetSymbolVersion --verbosity "minimal" --global + } +} + +try { + . $PSScriptRoot\post-build-utils.ps1 + + InstallDotnetSymbol + + foreach ($Job in @(Get-Job)) { + Remove-Job -Id $Job.Id + } + + CheckSymbolsAvailable +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message $_ + ExitWithExitCode 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/trigger-subscriptions.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/trigger-subscriptions.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/post-build/trigger-subscriptions.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/post-build/trigger-subscriptions.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,64 @@ +param( + [Parameter(Mandatory=$true)][string] $SourceRepo, + [Parameter(Mandatory=$true)][int] $ChannelId, + [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', + [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + # Get all the $SourceRepo subscriptions + $normalizedSourceRepo = $SourceRepo.Replace('dnceng@', '') + $subscriptions = Get-MaestroSubscriptions -SourceRepository $normalizedSourceRepo -ChannelId $ChannelId + + if (!$subscriptions) { + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message "No subscriptions found for source repo '$normalizedSourceRepo' in channel '$ChannelId'" + ExitWithExitCode 0 + } + + $subscriptionsToTrigger = New-Object System.Collections.Generic.List[string] + $failedTriggeredSubscription = $false + + # Get all enabled subscriptions that need dependency flow on 'everyBuild' + foreach ($subscription in $subscriptions) { + if ($subscription.enabled -and $subscription.policy.updateFrequency -like 'everyBuild' -and $subscription.channel.id -eq $ChannelId) { + Write-Host "Should trigger this subscription: ${$subscription.id}" + [void]$subscriptionsToTrigger.Add($subscription.id) + } + } + + foreach ($subscriptionToTrigger in $subscriptionsToTrigger) { + try { + Write-Host "Triggering subscription '$subscriptionToTrigger'." + + Trigger-Subscription -SubscriptionId $subscriptionToTrigger + + Write-Host 'done.' + } + catch + { + Write-Host "There was an error while triggering subscription '$subscriptionToTrigger'" + Write-Host $_ + Write-Host $_.ScriptStackTrace + $failedTriggeredSubscription = $true + } + } + + if ($subscriptionsToTrigger.Count -eq 0) { + Write-Host "No subscription matched source repo '$normalizedSourceRepo' and channel ID '$ChannelId'." + } + elseif ($failedTriggeredSubscription) { + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message 'At least one subscription failed to be triggered...' + ExitWithExitCode 1 + } + else { + Write-Host 'All subscriptions were triggered successfully!' + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message $_ + ExitWithExitCode 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/PSScriptAnalyzerSettings.psd1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/PSScriptAnalyzerSettings.psd1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/PSScriptAnalyzerSettings.psd1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/PSScriptAnalyzerSettings.psd1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +@{ + IncludeRules=@('PSAvoidUsingCmdletAliases', + 'PSAvoidUsingWMICmdlet', + 'PSAvoidUsingPositionalParameters', + 'PSAvoidUsingInvokeExpression', + 'PSUseDeclaredVarsMoreThanAssignments', + 'PSUseCmdletCorrectly', + 'PSStandardDSCFunctionsInResource', + 'PSUseIdenticalMandatoryParametersForDSC', + 'PSUseIdenticalParametersForDSC') +} \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/README.md dotnet8-8.0.100-8.0.0/src/aspire/eng/common/README.md --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/README.md 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/README.md 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,28 @@ +# Don't touch this folder + + uuuuuuuuuuuuuuuuuuuu + u" uuuuuuuuuuuuuuuuuu "u + u" u$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u + $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ + $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ + $ $$$" ... "$... ...$" ... "$$$ ... "$$$ $ + $ $$$u `"$$$$$$$ $$$ $$$$$ $$ $$$ $$$ $ + $ $$$$$$uu "$$$$ $$$ $$$$$ $$ """ u$$$ $ + $ $$$""$$$ $$$$ $$$u "$$$" u$$ $$$$$$$$ $ + $ $$$$....,$$$$$..$$$$$....,$$$$..$$$$$$$$ $ + $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ + "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$" u" + "u """""""""""""""""" u" + """""""""""""""""""" + +!!! Changes made in this directory are subject to being overwritten by automation !!! + +The files in this directory are shared by all Arcade repos and managed by automation. If you need to make changes to these files, open an issue or submit a pull request to https://github.com/dotnet/arcade first. diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/retain-build.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/retain-build.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/retain-build.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/retain-build.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,45 @@ + +Param( +[Parameter(Mandatory=$true)][int] $buildId, +[Parameter(Mandatory=$true)][string] $azdoOrgUri, +[Parameter(Mandatory=$true)][string] $azdoProject, +[Parameter(Mandatory=$true)][string] $token +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +function Get-AzDOHeaders( + [string] $token) +{ + $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":${token}")) + $headers = @{"Authorization"="Basic $base64AuthInfo"} + return $headers +} + +function Update-BuildRetention( + [string] $azdoOrgUri, + [string] $azdoProject, + [int] $buildId, + [string] $token) +{ + $headers = Get-AzDOHeaders -token $token + $requestBody = "{ + `"keepForever`": `"true`" + }" + + $requestUri = "${azdoOrgUri}/${azdoProject}/_apis/build/builds/${buildId}?api-version=6.0" + write-Host "Attempting to retain build using the following URI: ${requestUri} ..." + + try { + Invoke-RestMethod -Uri $requestUri -Method Patch -Body $requestBody -Header $headers -contentType "application/json" + Write-Host "Updated retention settings for build ${buildId}." + } + catch { + Write-Error "Failed to update retention settings for build: $_.Exception.Response.StatusDescription" + exit 1 + } +} + +Update-BuildRetention -azdoOrgUri $azdoOrgUri -azdoProject $azdoProject -buildId $buildId -token $token +exit 0 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdk-task.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdk-task.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdk-task.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdk-task.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,97 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string] $configuration = 'Debug', + [string] $task, + [string] $verbosity = 'minimal', + [string] $msbuildEngine = $null, + [switch] $restore, + [switch] $prepareMachine, + [switch] $help, + [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties +) + +$ci = $true +$binaryLog = $true +$warnAsError = $true + +. $PSScriptRoot\tools.ps1 + +function Print-Usage() { + Write-Host "Common settings:" + Write-Host " -task Name of Arcade task (name of a project in SdkTasks directory of the Arcade SDK package)" + Write-Host " -restore Restore dependencies" + Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]" + Write-Host " -help Print help and exit" + Write-Host "" + + Write-Host "Advanced settings:" + Write-Host " -prepareMachine Prepare machine for CI run" + Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." + Write-Host "" + Write-Host "Command line arguments not listed above are passed thru to msbuild." +} + +function Build([string]$target) { + $logSuffix = if ($target -eq 'Execute') { '' } else { ".$target" } + $log = Join-Path $LogDir "$task$logSuffix.binlog" + $outputPath = Join-Path $ToolsetDir "$task\" + + MSBuild $taskProject ` + /bl:$log ` + /t:$target ` + /p:Configuration=$configuration ` + /p:RepoRoot=$RepoRoot ` + /p:BaseIntermediateOutputPath=$outputPath ` + /v:$verbosity ` + @properties +} + +try { + if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { + Print-Usage + exit 0 + } + + if ($task -eq "") { + Write-PipelineTelemetryError -Category 'Build' -Message "Missing required parameter '-task '" + Print-Usage + ExitWithExitCode 1 + } + + if( $msbuildEngine -eq "vs") { + # Ensure desktop MSBuild is available for sdk tasks. + if( -not ($GlobalJson.tools.PSObject.Properties.Name -contains "vs" )) { + $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty + } + if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { + $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.8.1-2" -MemberType NoteProperty + } + if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") { + $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true + } + if ($xcopyMSBuildToolsFolder -eq $null) { + throw 'Unable to get xcopy downloadable version of msbuild' + } + + $global:_MSBuildExe = "$($xcopyMSBuildToolsFolder)\MSBuild\Current\Bin\MSBuild.exe" + } + + $taskProject = GetSdkTaskProject $task + if (!(Test-Path $taskProject)) { + Write-PipelineTelemetryError -Category 'Build' -Message "Unknown task: $task" + ExitWithExitCode 1 + } + + if ($restore) { + Build 'Restore' + } + + Build 'Execute' +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Build' -Message $_ + ExitWithExitCode 1 +} + +ExitWithExitCode 0 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/configure-sdl-tool.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/configure-sdl-tool.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/configure-sdl-tool.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/configure-sdl-tool.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,130 @@ +Param( + [string] $GuardianCliLocation, + [string] $WorkingDirectory, + [string] $TargetDirectory, + [string] $GdnFolder, + # The list of Guardian tools to configure. For each object in the array: + # - If the item is a [hashtable], it must contain these entries: + # - Name = The tool name as Guardian knows it. + # - Scenario = (Optional) Scenario-specific name for this configuration entry. It must be unique + # among all tool entries with the same Name. + # - Args = (Optional) Array of Guardian tool configuration args, like '@("Target > C:\temp")' + # - If the item is a [string] $v, it is treated as '@{ Name="$v" }' + [object[]] $ToolsList, + [string] $GuardianLoggerLevel='Standard', + # Optional: Additional params to add to any tool using CredScan. + [string[]] $CrScanAdditionalRunConfigParams, + # Optional: Additional params to add to any tool using PoliCheck. + [string[]] $PoliCheckAdditionalRunConfigParams, + # Optional: Additional params to add to any tool using CodeQL/Semmle. + [string[]] $CodeQLAdditionalRunConfigParams, + # Optional: Additional params to add to any tool using Binskim. + [string[]] $BinskimAdditionalRunConfigParams +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true +$global:LASTEXITCODE = 0 + +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + # Normalize tools list: all in [hashtable] form with defined values for each key. + $ToolsList = $ToolsList | + ForEach-Object { + if ($_ -is [string]) { + $_ = @{ Name = $_ } + } + + if (-not ($_['Scenario'])) { $_.Scenario = "" } + if (-not ($_['Args'])) { $_.Args = @() } + $_ + } + + Write-Host "List of tools to configure:" + $ToolsList | ForEach-Object { $_ | Out-String | Write-Host } + + # We store config files in the r directory of .gdn + $gdnConfigPath = Join-Path $GdnFolder 'r' + $ValidPath = Test-Path $GuardianCliLocation + + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location." + ExitWithExitCode 1 + } + + foreach ($tool in $ToolsList) { + # Put together the name and scenario to make a unique key. + $toolConfigName = $tool.Name + if ($tool.Scenario) { + $toolConfigName += "_" + $tool.Scenario + } + + Write-Host "=== Configuring $toolConfigName..." + + $gdnConfigFile = Join-Path $gdnConfigPath "$toolConfigName-configure.gdnconfig" + + # For some tools, add default and automatic args. + switch -Exact ($tool.Name) { + 'credscan' { + if ($targetDirectory) { + $tool.Args += "`"TargetDirectory < $TargetDirectory`"" + } + $tool.Args += "`"OutputType < pre`"" + $tool.Args += $CrScanAdditionalRunConfigParams + } + 'policheck' { + if ($targetDirectory) { + $tool.Args += "`"Target < $TargetDirectory`"" + } + $tool.Args += $PoliCheckAdditionalRunConfigParams + } + {$_ -in 'semmle', 'codeql'} { + if ($targetDirectory) { + $tool.Args += "`"SourceCodeDirectory < $TargetDirectory`"" + } + $tool.Args += $CodeQLAdditionalRunConfigParams + } + 'binskim' { + if ($targetDirectory) { + # Binskim crashes due to specific PDBs. GitHub issue: https://github.com/microsoft/binskim/issues/924. + # We are excluding all `_.pdb` files from the scan. + $tool.Args += "`"Target < $TargetDirectory\**;-:file|$TargetDirectory\**\_.pdb`"" + } + $tool.Args += $BinskimAdditionalRunConfigParams + } + } + + # Create variable pointing to the args array directly so we can use splat syntax later. + $toolArgs = $tool.Args + + # Configure the tool. If args array is provided or the current tool has some default arguments + # defined, add "--args" and splat each element on the end. Arg format is "{Arg id} < {Value}", + # one per parameter. Doc page for "guardian configure": + # https://dev.azure.com/securitytools/SecurityIntegration/_wiki/wikis/Guardian/1395/configure + Exec-BlockVerbosely { + & $GuardianCliLocation configure ` + --working-directory $WorkingDirectory ` + --tool $tool.Name ` + --output-path $gdnConfigFile ` + --logger-level $GuardianLoggerLevel ` + --noninteractive ` + --force ` + $(if ($toolArgs) { "--args" }) @toolArgs + Exit-IfNZEC "Sdl" + } + + Write-Host "Created '$toolConfigName' configuration file: $gdnConfigFile" + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/execute-all-sdl-tools.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/execute-all-sdl-tools.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/execute-all-sdl-tools.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/execute-all-sdl-tools.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,167 @@ +Param( + [string] $GuardianPackageName, # Required: the name of guardian CLI package (not needed if GuardianCliLocation is specified) + [string] $NugetPackageDirectory, # Required: directory where NuGet packages are installed (not needed if GuardianCliLocation is specified) + [string] $GuardianCliLocation, # Optional: Direct location of Guardian CLI executable if GuardianPackageName & NugetPackageDirectory are not specified + [string] $Repository=$env:BUILD_REPOSITORY_NAME, # Required: the name of the repository (e.g. dotnet/arcade) + [string] $BranchName=$env:BUILD_SOURCEBRANCH, # Optional: name of branch or version of gdn settings; defaults to master + [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, # Required: the directory where source files are located + [string] $ArtifactsDirectory = (Join-Path $env:BUILD_ARTIFACTSTAGINGDIRECTORY ('artifacts')), # Required: the directory where build artifacts are located + [string] $AzureDevOpsAccessToken, # Required: access token for dnceng; should be provided via KeyVault + + # Optional: list of SDL tools to run on source code. See 'configure-sdl-tool.ps1' for tools list + # format. + [object[]] $SourceToolsList, + # Optional: list of SDL tools to run on built artifacts. See 'configure-sdl-tool.ps1' for tools + # list format. + [object[]] $ArtifactToolsList, + # Optional: list of SDL tools to run without automatically specifying a target directory. See + # 'configure-sdl-tool.ps1' for tools list format. + [object[]] $CustomToolsList, + + [bool] $TsaPublish=$False, # Optional: true will publish results to TSA; only set to true after onboarding to TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaBranchName=$env:BUILD_SOURCEBRANCH, # Optional: required for TSA publish; defaults to $(Build.SourceBranchName); TSA is the automated framework used to upload test results as bugs. + [string] $TsaRepositoryName=$env:BUILD_REPOSITORY_NAME, # Optional: TSA repository name; will be generated automatically if not submitted; TSA is the automated framework used to upload test results as bugs. + [string] $BuildNumber=$env:BUILD_BUILDNUMBER, # Optional: required for TSA publish; defaults to $(Build.BuildNumber) + [bool] $UpdateBaseline=$False, # Optional: if true, will update the baseline in the repository; should only be run after fixing any issues which need to be fixed + [bool] $TsaOnboard=$False, # Optional: if true, will onboard the repository to TSA; should only be run once; TSA is the automated framework used to upload test results as bugs. + [string] $TsaInstanceUrl, # Optional: only needed if TsaOnboard or TsaPublish is true; the instance-url registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the codebase registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaProjectName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the project registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaNotificationEmail, # Optional: only needed if TsaOnboard is true; the email(s) which will receive notifications of TSA bug filings (e.g. alias@microsoft.com); TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseAdmin, # Optional: only needed if TsaOnboard is true; the aliases which are admins of the TSA codebase (e.g. DOMAIN\alias); TSA is the automated framework used to upload test results as bugs. + [string] $TsaBugAreaPath, # Optional: only needed if TsaOnboard is true; the area path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $TsaIterationPath, # Optional: only needed if TsaOnboard is true; the iteration path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $GuardianLoggerLevel='Standard', # Optional: the logger level for the Guardian CLI; options are Trace, Verbose, Standard, Warning, and Error + [string[]] $CrScanAdditionalRunConfigParams, # Optional: Additional Params to custom build a CredScan run config in the format @("xyz:abc","sdf:1") + [string[]] $PoliCheckAdditionalRunConfigParams, # Optional: Additional Params to custom build a Policheck run config in the format @("xyz:abc","sdf:1") + [string[]] $CodeQLAdditionalRunConfigParams, # Optional: Additional Params to custom build a Semmle/CodeQL run config in the format @("xyz < abc","sdf < 1") + [string[]] $BinskimAdditionalRunConfigParams, # Optional: Additional Params to custom build a Binskim run config in the format @("xyz < abc","sdf < 1") + [bool] $BreakOnFailure=$False # Optional: Fail the build if there were errors during the run +) + +try { + $ErrorActionPreference = 'Stop' + Set-StrictMode -Version 2.0 + $disableConfigureToolsetImport = $true + $global:LASTEXITCODE = 0 + + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + #Replace repo names to the format of org/repo + if (!($Repository.contains('/'))) { + $RepoName = $Repository -replace '(.*?)-(.*)', '$1/$2'; + } + else{ + $RepoName = $Repository; + } + + if ($GuardianPackageName) { + $guardianCliLocation = Join-Path $NugetPackageDirectory (Join-Path $GuardianPackageName (Join-Path 'tools' 'guardian.cmd')) + } else { + $guardianCliLocation = $GuardianCliLocation + } + + $workingDirectory = (Split-Path $SourceDirectory -Parent) + $ValidPath = Test-Path $guardianCliLocation + + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Invalid Guardian CLI Location.' + ExitWithExitCode 1 + } + + Exec-BlockVerbosely { + & $(Join-Path $PSScriptRoot 'init-sdl.ps1') -GuardianCliLocation $guardianCliLocation -Repository $RepoName -BranchName $BranchName -WorkingDirectory $workingDirectory -AzureDevOpsAccessToken $AzureDevOpsAccessToken -GuardianLoggerLevel $GuardianLoggerLevel + } + $gdnFolder = Join-Path $workingDirectory '.gdn' + + if ($TsaOnboard) { + if ($TsaCodebaseName -and $TsaNotificationEmail -and $TsaCodebaseAdmin -and $TsaBugAreaPath) { + Exec-BlockVerbosely { + & $guardianCliLocation tsa-onboard --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + } + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-onboard failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } else { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not onboard to TSA -- not all required values ($TsaCodebaseName, $TsaNotificationEmail, $TsaCodebaseAdmin, $TsaBugAreaPath) were specified.' + ExitWithExitCode 1 + } + } + + # Configure a list of tools with a default target directory. Populates the ".gdn/r" directory. + function Configure-ToolsList([object[]] $tools, [string] $targetDirectory) { + if ($tools -and $tools.Count -gt 0) { + Exec-BlockVerbosely { + & $(Join-Path $PSScriptRoot 'configure-sdl-tool.ps1') ` + -GuardianCliLocation $guardianCliLocation ` + -WorkingDirectory $workingDirectory ` + -TargetDirectory $targetDirectory ` + -GdnFolder $gdnFolder ` + -ToolsList $tools ` + -AzureDevOpsAccessToken $AzureDevOpsAccessToken ` + -GuardianLoggerLevel $GuardianLoggerLevel ` + -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams ` + -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams ` + -CodeQLAdditionalRunConfigParams $CodeQLAdditionalRunConfigParams ` + -BinskimAdditionalRunConfigParams $BinskimAdditionalRunConfigParams + if ($BreakOnFailure) { + Exit-IfNZEC "Sdl" + } + } + } + } + + # Configure Artifact and Source tools with default Target directories. + Configure-ToolsList $ArtifactToolsList $ArtifactsDirectory + Configure-ToolsList $SourceToolsList $SourceDirectory + # Configure custom tools with no default Target directory. + Configure-ToolsList $CustomToolsList $null + + # At this point, all tools are configured in the ".gdn" directory. Run them all in a single call. + # (If we used "run" multiple times, each run would overwrite data from earlier runs.) + Exec-BlockVerbosely { + & $(Join-Path $PSScriptRoot 'run-sdl.ps1') ` + -GuardianCliLocation $guardianCliLocation ` + -WorkingDirectory $SourceDirectory ` + -UpdateBaseline $UpdateBaseline ` + -GdnFolder $gdnFolder + } + + if ($TsaPublish) { + if ($TsaBranchName -and $BuildNumber) { + if (-not $TsaRepositoryName) { + $TsaRepositoryName = "$($Repository)-$($BranchName)" + } + Exec-BlockVerbosely { + & $guardianCliLocation tsa-publish --all-tools --repository-name "$TsaRepositoryName" --branch-name "$TsaBranchName" --build-number "$BuildNumber" --onboard $True --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + } + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-publish failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } else { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not publish to TSA -- not all required values ($TsaBranchName, $BuildNumber) were specified.' + ExitWithExitCode 1 + } + } + + if ($BreakOnFailure) { + Write-Host "Failing the build in case of breaking results..." + Exec-BlockVerbosely { + & $guardianCliLocation break --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + } + } else { + Write-Host "Letting the build pass even if there were breaking results..." + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + exit 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/extract-artifact-archives.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/extract-artifact-archives.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/extract-artifact-archives.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/extract-artifact-archives.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,63 @@ +# This script looks for each archive file in a directory and extracts it into the target directory. +# For example, the file "$InputPath/bin.tar.gz" extracts to "$ExtractPath/bin.tar.gz.extracted/**". +# Uses the "tar" utility added to Windows 10 / Windows 2019 that supports tar.gz and zip. +param( + # Full path to directory where archives are stored. + [Parameter(Mandatory=$true)][string] $InputPath, + # Full path to directory to extract archives into. May be the same as $InputPath. + [Parameter(Mandatory=$true)][string] $ExtractPath +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +$disableConfigureToolsetImport = $true + +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + Measure-Command { + $jobs = @() + + # Find archive files for non-Windows and Windows builds. + $archiveFiles = @( + Get-ChildItem (Join-Path $InputPath "*.tar.gz") + Get-ChildItem (Join-Path $InputPath "*.zip") + ) + + foreach ($targzFile in $archiveFiles) { + $jobs += Start-Job -ScriptBlock { + $file = $using:targzFile + $fileName = [System.IO.Path]::GetFileName($file) + $extractDir = Join-Path $using:ExtractPath "$fileName.extracted" + + New-Item $extractDir -ItemType Directory -Force | Out-Null + + Write-Host "Extracting '$file' to '$extractDir'..." + + # Pipe errors to stdout to prevent PowerShell detecting them and quitting the job early. + # This type of quit skips the catch, so we wouldn't be able to tell which file triggered the + # error. Save output so it can be stored in the exception string along with context. + $output = tar -xf $file -C $extractDir 2>&1 + # Handle NZEC manually rather than using Exit-IfNZEC: we are in a background job, so we + # don't have access to the outer scope. + if ($LASTEXITCODE -ne 0) { + throw "Error extracting '$file': non-zero exit code ($LASTEXITCODE). Output: '$output'" + } + + Write-Host "Extracted to $extractDir" + } + } + + Receive-Job $jobs -Wait + } +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/extract-artifact-packages.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/extract-artifact-packages.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/extract-artifact-packages.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/extract-artifact-packages.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,82 @@ +param( + [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where artifact packages are stored + [Parameter(Mandatory=$true)][string] $ExtractPath # Full path to directory where the packages will be extracted +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +$disableConfigureToolsetImport = $true + +function ExtractArtifacts { + if (!(Test-Path $InputPath)) { + Write-Host "Input Path does not exist: $InputPath" + ExitWithExitCode 0 + } + $Jobs = @() + Get-ChildItem "$InputPath\*.nupkg" | + ForEach-Object { + $Jobs += Start-Job -ScriptBlock $ExtractPackage -ArgumentList $_.FullName + } + + foreach ($Job in $Jobs) { + Wait-Job -Id $Job.Id | Receive-Job + } +} + +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + $ExtractPackage = { + param( + [string] $PackagePath # Full path to a NuGet package + ) + + if (!(Test-Path $PackagePath)) { + Write-PipelineTelemetryError -Category 'Build' -Message "Input file does not exist: $PackagePath" + ExitWithExitCode 1 + } + + $RelevantExtensions = @('.dll', '.exe', '.pdb') + Write-Host -NoNewLine 'Extracting ' ([System.IO.Path]::GetFileName($PackagePath)) '...' + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + [System.IO.Directory]::CreateDirectory($ExtractPath); + + try { + $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) + + $zip.Entries | + Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | + ForEach-Object { + $TargetPath = Join-Path -Path $ExtractPath -ChildPath (Split-Path -Path $_.FullName) + [System.IO.Directory]::CreateDirectory($TargetPath); + + $TargetFile = Join-Path -Path $ExtractPath -ChildPath $_.FullName + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile) + } + } + catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 + } + finally { + $zip.Dispose() + } + } + Measure-Command { ExtractArtifacts } +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/init-sdl.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/init-sdl.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/init-sdl.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/init-sdl.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,55 @@ +Param( + [string] $GuardianCliLocation, + [string] $Repository, + [string] $BranchName='master', + [string] $WorkingDirectory, + [string] $AzureDevOpsAccessToken, + [string] $GuardianLoggerLevel='Standard' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true +$global:LASTEXITCODE = 0 + +# `tools.ps1` checks $ci to perform some actions. Since the SDL +# scripts don't necessarily execute in the same agent that run the +# build.ps1/sh script this variable isn't automatically set. +$ci = $true +. $PSScriptRoot\..\tools.ps1 + +# Don't display the console progress UI - it's a huge perf hit +$ProgressPreference = 'SilentlyContinue' + +# Construct basic auth from AzDO access token; construct URI to the repository's gdn folder stored in that repository; construct location of zip file +$encodedPat = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$AzureDevOpsAccessToken")) +$escapedRepository = [Uri]::EscapeDataString("/$Repository/$BranchName/.gdn") +$uri = "https://dev.azure.com/dnceng/internal/_apis/git/repositories/sdl-tool-cfg/Items?path=$escapedRepository&versionDescriptor[versionOptions]=0&`$format=zip&api-version=5.0" +$zipFile = "$WorkingDirectory/gdn.zip" + +Add-Type -AssemblyName System.IO.Compression.FileSystem +$gdnFolder = (Join-Path $WorkingDirectory '.gdn') + +try { + # if the folder does not exist, we'll do a guardian init and push it to the remote repository + Write-Host 'Initializing Guardian...' + Write-Host "$GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel" + & $GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian init failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + # We create the mainbaseline so it can be edited later + Write-Host "$GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline" + & $GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian baseline failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + ExitWithExitCode 0 +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/NuGet.config dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/NuGet.config --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/NuGet.config 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/NuGet.config 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/packages.config dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/packages.config --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/packages.config 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/packages.config 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,4 @@ + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/run-sdl.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/run-sdl.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/run-sdl.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/run-sdl.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,49 @@ +Param( + [string] $GuardianCliLocation, + [string] $WorkingDirectory, + [string] $GdnFolder, + [string] $UpdateBaseline, + [string] $GuardianLoggerLevel='Standard' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true +$global:LASTEXITCODE = 0 + +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + # We store config files in the r directory of .gdn + $gdnConfigPath = Join-Path $GdnFolder 'r' + $ValidPath = Test-Path $GuardianCliLocation + + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location." + ExitWithExitCode 1 + } + + $gdnConfigFiles = Get-ChildItem $gdnConfigPath -Recurse -Include '*.gdnconfig' + Write-Host "Discovered Guardian config files:" + $gdnConfigFiles | Out-String | Write-Host + + Exec-BlockVerbosely { + & $GuardianCliLocation run ` + --working-directory $WorkingDirectory ` + --baseline mainbaseline ` + --update-baseline $UpdateBaseline ` + --logger-level $GuardianLoggerLevel ` + --config @gdnConfigFiles + Exit-IfNZEC "Sdl" + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/sdl.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/sdl.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/sdl.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/sdl.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,38 @@ + +function Install-Gdn { + param( + [Parameter(Mandatory=$true)] + [string]$Path, + + # If omitted, install the latest version of Guardian, otherwise install that specific version. + [string]$Version + ) + + $ErrorActionPreference = 'Stop' + Set-StrictMode -Version 2.0 + $disableConfigureToolsetImport = $true + $global:LASTEXITCODE = 0 + + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + $argumentList = @("install", "Microsoft.Guardian.Cli", "-Source https://securitytools.pkgs.visualstudio.com/_packaging/Guardian/nuget/v3/index.json", "-OutputDirectory $Path", "-NonInteractive", "-NoCache") + + if ($Version) { + $argumentList += "-Version $Version" + } + + Start-Process nuget -Verbose -ArgumentList $argumentList -NoNewWindow -Wait + + $gdnCliPath = Get-ChildItem -Filter guardian.cmd -Recurse -Path $Path + + if (!$gdnCliPath) + { + Write-PipelineTelemetryError -Category 'Sdl' -Message 'Failure installing Guardian' + } + + return $gdnCliPath.FullName +} \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/trim-assets-version.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/trim-assets-version.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/sdl/trim-assets-version.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/sdl/trim-assets-version.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,75 @@ +<# +.SYNOPSIS +Install and run the 'Microsoft.DotNet.VersionTools.Cli' tool with the 'trim-artifacts-version' command to trim the version from the NuGet assets file name. + +.PARAMETER InputPath +Full path to directory where artifact packages are stored + +.PARAMETER Recursive +Search for NuGet packages recursively + +#> + +Param( + [string] $InputPath, + [bool] $Recursive = $true +) + +$CliToolName = "Microsoft.DotNet.VersionTools.Cli" + +function Install-VersionTools-Cli { + param( + [Parameter(Mandatory=$true)][string]$Version + ) + + Write-Host "Installing the package '$CliToolName' with a version of '$version' ..." + $feed = "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" + + $argumentList = @("tool", "install", "--local", "$CliToolName", "--add-source $feed", "--no-cache", "--version $Version", "--create-manifest-if-needed") + Start-Process "$dotnet" -Verbose -ArgumentList $argumentList -NoNewWindow -Wait +} + +# ------------------------------------------------------------------- + +if (!(Test-Path $InputPath)) { + Write-Host "Input Path '$InputPath' does not exist" + ExitWithExitCode 1 +} + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +$disableConfigureToolsetImport = $true +$global:LASTEXITCODE = 0 + +# `tools.ps1` checks $ci to perform some actions. Since the SDL +# scripts don't necessarily execute in the same agent that run the +# build.ps1/sh script this variable isn't automatically set. +$ci = $true +. $PSScriptRoot\..\tools.ps1 + +try { + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + + $toolsetVersion = Read-ArcadeSdkVersion + Install-VersionTools-Cli -Version $toolsetVersion + + $cliToolFound = (& "$dotnet" tool list --local | Where-Object {$_.Split(' ')[0] -eq $CliToolName}) + if ($null -eq $cliToolFound) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "The '$CliToolName' tool is not installed." + ExitWithExitCode 1 + } + + Exec-BlockVerbosely { + & "$dotnet" $CliToolName trim-assets-version ` + --assets-path $InputPath ` + --recursive $Recursive + Exit-IfNZEC "Sdl" + } +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/SetupNugetSources.ps1 dotnet8-8.0.100-8.0.0/src/aspire/eng/common/SetupNugetSources.ps1 --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/SetupNugetSources.ps1 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/SetupNugetSources.ps1 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,167 @@ +# This file is a temporary workaround for internal builds to be able to restore from private AzDO feeds. +# This file should be removed as part of this issue: https://github.com/dotnet/arcade/issues/4080 +# +# What the script does is iterate over all package sources in the pointed NuGet.config and add a credential entry +# under for each Maestro managed private feed. Two additional credential +# entries are also added for the two private static internal feeds: dotnet3-internal and dotnet3-internal-transport. +# +# This script needs to be called in every job that will restore packages and which the base repo has +# private AzDO feeds in the NuGet.config. +# +# See example YAML call for this script below. Note the use of the variable `$(dn-bot-dnceng-artifact-feeds-rw)` +# from the AzureDevOps-Artifact-Feeds-Pats variable group. +# +# Any disabledPackageSources entries which start with "darc-int" will be re-enabled as part of this script executing +# +# - task: PowerShell@2 +# displayName: Setup Private Feeds Credentials +# condition: eq(variables['Agent.OS'], 'Windows_NT') +# inputs: +# filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 +# arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token +# env: +# Token: $(dn-bot-dnceng-artifact-feeds-rw) + +[CmdletBinding()] +param ( + [Parameter(Mandatory = $true)][string]$ConfigFile, + [Parameter(Mandatory = $true)][string]$Password +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +. $PSScriptRoot\tools.ps1 + +# Add source entry to PackageSources +function AddPackageSource($sources, $SourceName, $SourceEndPoint, $creds, $Username, $Password) { + $packageSource = $sources.SelectSingleNode("add[@key='$SourceName']") + + if ($packageSource -eq $null) + { + $packageSource = $doc.CreateElement("add") + $packageSource.SetAttribute("key", $SourceName) + $packageSource.SetAttribute("value", $SourceEndPoint) + $sources.AppendChild($packageSource) | Out-Null + } + else { + Write-Host "Package source $SourceName already present." + } + + AddCredential -Creds $creds -Source $SourceName -Username $Username -Password $Password +} + +# Add a credential node for the specified source +function AddCredential($creds, $source, $username, $password) { + # Looks for credential configuration for the given SourceName. Create it if none is found. + $sourceElement = $creds.SelectSingleNode($Source) + if ($sourceElement -eq $null) + { + $sourceElement = $doc.CreateElement($Source) + $creds.AppendChild($sourceElement) | Out-Null + } + + # Add the node to the credential if none is found. + $usernameElement = $sourceElement.SelectSingleNode("add[@key='Username']") + if ($usernameElement -eq $null) + { + $usernameElement = $doc.CreateElement("add") + $usernameElement.SetAttribute("key", "Username") + $sourceElement.AppendChild($usernameElement) | Out-Null + } + $usernameElement.SetAttribute("value", $Username) + + # Add the to the credential if none is found. + # Add it as a clear text because there is no support for encrypted ones in non-windows .Net SDKs. + # -> https://github.com/NuGet/Home/issues/5526 + $passwordElement = $sourceElement.SelectSingleNode("add[@key='ClearTextPassword']") + if ($passwordElement -eq $null) + { + $passwordElement = $doc.CreateElement("add") + $passwordElement.SetAttribute("key", "ClearTextPassword") + $sourceElement.AppendChild($passwordElement) | Out-Null + } + $passwordElement.SetAttribute("value", $Password) +} + +function InsertMaestroPrivateFeedCredentials($Sources, $Creds, $Username, $Password) { + $maestroPrivateSources = $Sources.SelectNodes("add[contains(@key,'darc-int')]") + + Write-Host "Inserting credentials for $($maestroPrivateSources.Count) Maestro's private feeds." + + ForEach ($PackageSource in $maestroPrivateSources) { + Write-Host "`tInserting credential for Maestro's feed:" $PackageSource.Key + AddCredential -Creds $creds -Source $PackageSource.Key -Username $Username -Password $Password + } +} + +function EnablePrivatePackageSources($DisabledPackageSources) { + $maestroPrivateSources = $DisabledPackageSources.SelectNodes("add[contains(@key,'darc-int')]") + ForEach ($DisabledPackageSource in $maestroPrivateSources) { + Write-Host "`tEnsuring private source '$($DisabledPackageSource.key)' is enabled by deleting it from disabledPackageSource" + # Due to https://github.com/NuGet/Home/issues/10291, we must actually remove the disabled entries + $DisabledPackageSources.RemoveChild($DisabledPackageSource) + } +} + +if (!(Test-Path $ConfigFile -PathType Leaf)) { + Write-PipelineTelemetryError -Category 'Build' -Message "Eng/common/SetupNugetSources.ps1 returned a non-zero exit code. Couldn't find the NuGet config file: $ConfigFile" + ExitWithExitCode 1 +} + +if (!$Password) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Eng/common/SetupNugetSources.ps1 returned a non-zero exit code. Please supply a valid PAT' + ExitWithExitCode 1 +} + +# Load NuGet.config +$doc = New-Object System.Xml.XmlDocument +$filename = (Get-Item $ConfigFile).FullName +$doc.Load($filename) + +# Get reference to or create one if none exist already +$sources = $doc.DocumentElement.SelectSingleNode("packageSources") +if ($sources -eq $null) { + $sources = $doc.CreateElement("packageSources") + $doc.DocumentElement.AppendChild($sources) | Out-Null +} + +# Looks for a node. Create it if none is found. +$creds = $doc.DocumentElement.SelectSingleNode("packageSourceCredentials") +if ($creds -eq $null) { + $creds = $doc.CreateElement("packageSourceCredentials") + $doc.DocumentElement.AppendChild($creds) | Out-Null +} + +# Check for disabledPackageSources; we'll enable any darc-int ones we find there +$disabledSources = $doc.DocumentElement.SelectSingleNode("disabledPackageSources") +if ($disabledSources -ne $null) { + Write-Host "Checking for any darc-int disabled package sources in the disabledPackageSources node" + EnablePrivatePackageSources -DisabledPackageSources $disabledSources +} + +$userName = "dn-bot" + +# Insert credential nodes for Maestro's private feeds +InsertMaestroPrivateFeedCredentials -Sources $sources -Creds $creds -Username $userName -Password $Password + +# 3.1 uses a different feed url format so it's handled differently here +$dotnet31Source = $sources.SelectSingleNode("add[@key='dotnet3.1']") +if ($dotnet31Source -ne $null) { + AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v2" -Creds $creds -Username $userName -Password $Password + AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v2" -Creds $creds -Username $userName -Password $Password +} + +$dotnetVersions = @('5','6','7','8') + +foreach ($dotnetVersion in $dotnetVersions) { + $feedPrefix = "dotnet" + $dotnetVersion; + $dotnetSource = $sources.SelectSingleNode("add[@key='$feedPrefix']") + if ($dotnetSource -ne $null) { + AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal/nuget/v2" -Creds $creds -Username $userName -Password $Password + AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal-transport/nuget/v2" -Creds $creds -Username $userName -Password $Password + } +} + +$doc.Save($filename) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/SetupNugetSources.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/SetupNugetSources.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/SetupNugetSources.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/SetupNugetSources.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,171 @@ +#!/usr/bin/env bash + +# This file is a temporary workaround for internal builds to be able to restore from private AzDO feeds. +# This file should be removed as part of this issue: https://github.com/dotnet/arcade/issues/4080 +# +# What the script does is iterate over all package sources in the pointed NuGet.config and add a credential entry +# under for each Maestro's managed private feed. Two additional credential +# entries are also added for the two private static internal feeds: dotnet3-internal and dotnet3-internal-transport. +# +# This script needs to be called in every job that will restore packages and which the base repo has +# private AzDO feeds in the NuGet.config. +# +# See example YAML call for this script below. Note the use of the variable `$(dn-bot-dnceng-artifact-feeds-rw)` +# from the AzureDevOps-Artifact-Feeds-Pats variable group. +# +# Any disabledPackageSources entries which start with "darc-int" will be re-enabled as part of this script executing. +# +# - task: Bash@3 +# displayName: Setup Private Feeds Credentials +# inputs: +# filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh +# arguments: $(Build.SourcesDirectory)/NuGet.config $Token +# condition: ne(variables['Agent.OS'], 'Windows_NT') +# env: +# Token: $(dn-bot-dnceng-artifact-feeds-rw) + +ConfigFile=$1 +CredToken=$2 +NL='\n' +TB=' ' + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/tools.sh" + +if [ ! -f "$ConfigFile" ]; then + Write-PipelineTelemetryError -Category 'Build' "Error: Eng/common/SetupNugetSources.sh returned a non-zero exit code. Couldn't find the NuGet config file: $ConfigFile" + ExitWithExitCode 1 +fi + +if [ -z "$CredToken" ]; then + Write-PipelineTelemetryError -category 'Build' "Error: Eng/common/SetupNugetSources.sh returned a non-zero exit code. Please supply a valid PAT" + ExitWithExitCode 1 +fi + +if [[ `uname -s` == "Darwin" ]]; then + NL=$'\\\n' + TB='' +fi + +# Ensure there is a ... section. +grep -i "" $ConfigFile +if [ "$?" != "0" ]; then + echo "Adding ... section." + ConfigNodeHeader="" + PackageSourcesTemplate="${TB}${NL}${TB}" + + sed -i.bak "s|$ConfigNodeHeader|$ConfigNodeHeader${NL}$PackageSourcesTemplate|" $ConfigFile +fi + +# Ensure there is a ... section. +grep -i "" $ConfigFile +if [ "$?" != "0" ]; then + echo "Adding ... section." + + PackageSourcesNodeFooter="" + PackageSourceCredentialsTemplate="${TB}${NL}${TB}" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourcesNodeFooter${NL}$PackageSourceCredentialsTemplate|" $ConfigFile +fi + +PackageSources=() + +# Ensure dotnet3.1-internal and dotnet3.1-internal-transport are in the packageSources if the public dotnet3.1 feeds are present +grep -i "" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=('dotnet3.1-internal') + + grep -i "" $ConfigFile + if [ "$?" != "0" ]; then + echo "Adding dotnet3.1-internal-transport to the packageSources." + PackageSourcesNodeFooter="" + PackageSourceTemplate="${TB}" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=('dotnet3.1-internal-transport') +fi + +DotNetVersions=('5' '6' '7' '8') + +for DotNetVersion in ${DotNetVersions[@]} ; do + FeedPrefix="dotnet${DotNetVersion}"; + grep -i "" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=("$FeedPrefix-internal") + + grep -i "" $ConfigFile + if [ "$?" != "0" ]; then + echo "Adding $FeedPrefix-internal-transport to the packageSources." + PackageSourcesNodeFooter="" + PackageSourceTemplate="${TB}" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=("$FeedPrefix-internal-transport") + fi +done + +# I want things split line by line +PrevIFS=$IFS +IFS=$'\n' +PackageSources+="$IFS" +PackageSources+=$(grep -oh '"darc-int-[^"]*"' $ConfigFile | tr -d '"') +IFS=$PrevIFS + +for FeedName in ${PackageSources[@]} ; do + # Check if there is no existing credential for this FeedName + grep -i "<$FeedName>" $ConfigFile + if [ "$?" != "0" ]; then + echo "Adding credentials for $FeedName." + + PackageSourceCredentialsNodeFooter="" + NewCredential="${TB}${TB}<$FeedName>${NL}${NL}${NL}" + + sed -i.bak "s|$PackageSourceCredentialsNodeFooter|$NewCredential${NL}$PackageSourceCredentialsNodeFooter|" $ConfigFile + fi +done + +# Re-enable any entries in disabledPackageSources where the feed name contains darc-int +grep -i "" $ConfigFile +if [ "$?" == "0" ]; then + DisabledDarcIntSources=() + echo "Re-enabling any disabled \"darc-int\" package sources in $ConfigFile" + DisabledDarcIntSources+=$(grep -oh '"darc-int-[^"]*" value="true"' $ConfigFile | tr -d '"') + for DisabledSourceName in ${DisabledDarcIntSources[@]} ; do + if [[ $DisabledSourceName == darc-int* ]] + then + OldDisableValue="" + NewDisableValue="" + sed -i.bak "s|$OldDisableValue|$NewDisableValue|" $ConfigFile + echo "Neutralized disablePackageSources entry for '$DisabledSourceName'" + fi + done +fi diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/job/execute-sdl.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/job/execute-sdl.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/job/execute-sdl.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/job/execute-sdl.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,139 @@ +parameters: + enable: 'false' # Whether the SDL validation job should execute or not + overrideParameters: '' # Optional: to override values for parameters. + additionalParameters: '' # Optional: parameters that need user specific values eg: '-SourceToolsList @("abc","def") -ArtifactToolsList @("ghi","jkl")' + # Optional: if specified, restore and use this version of Guardian instead of the default. + overrideGuardianVersion: '' + # Optional: if true, publish the '.gdn' folder as a pipeline artifact. This can help with in-depth + # diagnosis of problems with specific tool configurations. + publishGuardianDirectoryToPipeline: false + # The script to run to execute all SDL tools. Use this if you want to use a script to define SDL + # parameters rather than relying on YAML. It may be better to use a local script, because you can + # reproduce results locally without piecing together a command based on the YAML. + executeAllSdlToolsScript: 'eng/common/sdl/execute-all-sdl-tools.ps1' + # There is some sort of bug (has been reported) in Azure DevOps where if this parameter is named + # 'continueOnError', the parameter value is not correctly picked up. + # This can also be remedied by the caller (post-build.yml) if it does not use a nested parameter + sdlContinueOnError: false # optional: determines whether to continue the build if the step errors; + # optional: determines if build artifacts should be downloaded. + downloadArtifacts: true + # optional: determines if this job should search the directory of downloaded artifacts for + # 'tar.gz' and 'zip' archive files and extract them before running SDL validation tasks. + extractArchiveArtifacts: false + dependsOn: '' # Optional: dependencies of the job + artifactNames: '' # Optional: patterns supplied to DownloadBuildArtifacts + # Usage: + # artifactNames: + # - 'BlobArtifacts' + # - 'Artifacts_Windows_NT_Release' + # Optional: download a list of pipeline artifacts. 'downloadArtifacts' controls build artifacts, + # not pipeline artifacts, so doesn't affect the use of this parameter. + pipelineArtifactNames: [] + +jobs: +- job: Run_SDL + dependsOn: ${{ parameters.dependsOn }} + displayName: Run SDL tool + condition: and(succeededOrFailed(), eq( ${{ parameters.enable }}, 'true')) + variables: + - group: DotNet-VSTS-Bot + - name: AzDOProjectName + value: ${{ parameters.AzDOProjectName }} + - name: AzDOPipelineId + value: ${{ parameters.AzDOPipelineId }} + - name: AzDOBuildId + value: ${{ parameters.AzDOBuildId }} + - template: /eng/common/templates/variables/sdl-variables.yml + - name: GuardianVersion + value: ${{ coalesce(parameters.overrideGuardianVersion, '$(DefaultGuardianVersion)') }} + - template: /eng/common/templates/variables/pool-providers.yml + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + steps: + - checkout: self + clean: true + + # If the template caller didn't provide an AzDO parameter, set them all up as Maestro vars. + - ${{ if not(and(parameters.AzDOProjectName, parameters.AzDOPipelineId, parameters.AzDOBuildId)) }}: + - template: /eng/common/templates/post-build/setup-maestro-vars.yml + + - ${{ if ne(parameters.downloadArtifacts, 'false')}}: + - ${{ if ne(parameters.artifactNames, '') }}: + - ${{ each artifactName in parameters.artifactNames }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Build Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: ${{ artifactName }} + downloadPath: $(Build.ArtifactStagingDirectory)\artifacts + checkDownloadedFiles: true + - ${{ if eq(parameters.artifactNames, '') }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Build Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: specific files + itemPattern: "**" + downloadPath: $(Build.ArtifactStagingDirectory)\artifacts + checkDownloadedFiles: true + + - ${{ each artifactName in parameters.pipelineArtifactNames }}: + - task: DownloadPipelineArtifact@2 + displayName: Download Pipeline Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: ${{ artifactName }} + downloadPath: $(Build.ArtifactStagingDirectory)\artifacts + checkDownloadedFiles: true + + - powershell: eng/common/sdl/trim-assets-version.ps1 + -InputPath $(Build.ArtifactStagingDirectory)\artifacts + displayName: Trim the version from the NuGet packages + continueOnError: ${{ parameters.sdlContinueOnError }} + + - powershell: eng/common/sdl/extract-artifact-packages.ps1 + -InputPath $(Build.ArtifactStagingDirectory)\artifacts\BlobArtifacts + -ExtractPath $(Build.ArtifactStagingDirectory)\artifacts\BlobArtifacts + displayName: Extract Blob Artifacts + continueOnError: ${{ parameters.sdlContinueOnError }} + + - powershell: eng/common/sdl/extract-artifact-packages.ps1 + -InputPath $(Build.ArtifactStagingDirectory)\artifacts\PackageArtifacts + -ExtractPath $(Build.ArtifactStagingDirectory)\artifacts\PackageArtifacts + displayName: Extract Package Artifacts + continueOnError: ${{ parameters.sdlContinueOnError }} + + - ${{ if ne(parameters.extractArchiveArtifacts, 'false') }}: + - powershell: eng/common/sdl/extract-artifact-archives.ps1 + -InputPath $(Build.ArtifactStagingDirectory)\artifacts + -ExtractPath $(Build.ArtifactStagingDirectory)\artifacts + displayName: Extract Archive Artifacts + continueOnError: ${{ parameters.sdlContinueOnError }} + + - template: /eng/common/templates/steps/execute-sdl.yml + parameters: + overrideGuardianVersion: ${{ parameters.overrideGuardianVersion }} + executeAllSdlToolsScript: ${{ parameters.executeAllSdlToolsScript }} + overrideParameters: ${{ parameters.overrideParameters }} + additionalParameters: ${{ parameters.additionalParameters }} + publishGuardianDirectoryToPipeline: ${{ parameters.publishGuardianDirectoryToPipeline }} + sdlContinueOnError: ${{ parameters.sdlContinueOnError }} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/job/job.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/job/job.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/job/job.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/job/job.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,255 @@ +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +parameters: +# Job schema parameters - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + cancelTimeoutInMinutes: '' + condition: '' + container: '' + continueOnError: false + dependsOn: '' + displayName: '' + pool: '' + steps: [] + strategy: '' + timeoutInMinutes: '' + variables: [] + workspace: '' + +# Job base template specific parameters + # See schema documentation - https://github.com/dotnet/arcade/blob/master/Documentation/AzureDevOps/TemplateSchema.md + artifacts: '' + enableMicrobuild: false + enablePublishBuildArtifacts: false + enablePublishBuildAssets: false + enablePublishTestResults: false + enablePublishUsingPipelines: false + enableBuildRetry: false + disableComponentGovernance: '' + componentGovernanceIgnoreDirectories: '' + mergeTestResults: false + testRunTitle: '' + testResultsFormat: '' + name: '' + preSteps: [] + runAsPublic: false +# Sbom related params + enableSbom: true + PackageVersion: 7.0.0 + BuildDropPath: '$(Build.SourcesDirectory)/artifacts' + +jobs: +- job: ${{ parameters.name }} + + ${{ if ne(parameters.cancelTimeoutInMinutes, '') }}: + cancelTimeoutInMinutes: ${{ parameters.cancelTimeoutInMinutes }} + + ${{ if ne(parameters.condition, '') }}: + condition: ${{ parameters.condition }} + + ${{ if ne(parameters.container, '') }}: + container: ${{ parameters.container }} + + ${{ if ne(parameters.continueOnError, '') }}: + continueOnError: ${{ parameters.continueOnError }} + + ${{ if ne(parameters.dependsOn, '') }}: + dependsOn: ${{ parameters.dependsOn }} + + ${{ if ne(parameters.displayName, '') }}: + displayName: ${{ parameters.displayName }} + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + + ${{ if ne(parameters.strategy, '') }}: + strategy: ${{ parameters.strategy }} + + ${{ if ne(parameters.timeoutInMinutes, '') }}: + timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + + variables: + - ${{ if ne(parameters.enableTelemetry, 'false') }}: + - name: DOTNET_CLI_TELEMETRY_PROFILE + value: '$(Build.Repository.Uri)' + - ${{ if eq(parameters.enableRichCodeNavigation, 'true') }}: + - name: EnableRichCodeNavigation + value: 'true' + # Retry signature validation up to three times, waiting 2 seconds between attempts. + # See https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu3028#retry-untrusted-root-failures + - name: NUGET_EXPERIMENTAL_CHAIN_BUILD_RETRY_POLICY + value: 3,2000 + - ${{ each variable in parameters.variables }}: + # handle name-value variable syntax + # example: + # - name: [key] + # value: [value] + - ${{ if ne(variable.name, '') }}: + - name: ${{ variable.name }} + value: ${{ variable.value }} + + # handle variable groups + - ${{ if ne(variable.group, '') }}: + - group: ${{ variable.group }} + + # handle template variable syntax + # example: + # - template: path/to/template.yml + # parameters: + # [key]: [value] + - ${{ if ne(variable.template, '') }}: + - template: ${{ variable.template }} + ${{ if ne(variable.parameters, '') }}: + parameters: ${{ variable.parameters }} + + # handle key-value variable syntax. + # example: + # - [key]: [value] + - ${{ if and(eq(variable.name, ''), eq(variable.group, ''), eq(variable.template, '')) }}: + - ${{ each pair in variable }}: + - name: ${{ pair.key }} + value: ${{ pair.value }} + + # DotNet-HelixApi-Access provides 'HelixApiAccessToken' for internal builds + - ${{ if and(eq(parameters.enableTelemetry, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: DotNet-HelixApi-Access + + ${{ if ne(parameters.workspace, '') }}: + workspace: ${{ parameters.workspace }} + + steps: + - ${{ if ne(parameters.preSteps, '') }}: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - task: MicroBuildSigningPlugin@3 + displayName: Install MicroBuild plugin + inputs: + signType: $(_SignType) + zipSources: false + feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json + env: + TeamName: $(_TeamName) + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + + - ${{ if and(eq(parameters.runAsPublic, 'false'), eq(variables['System.TeamProject'], 'internal')) }}: + - task: NuGetAuthenticate@0 + + - ${{ if and(ne(parameters.artifacts.download, 'false'), ne(parameters.artifacts.download, '')) }}: + - task: DownloadPipelineArtifact@2 + inputs: + buildType: current + artifactName: ${{ coalesce(parameters.artifacts.download.name, 'Artifacts_$(Agent.OS)_$(_BuildConfig)') }} + targetPath: ${{ coalesce(parameters.artifacts.download.path, 'artifacts') }} + itemPattern: ${{ coalesce(parameters.artifacts.download.pattern, '**') }} + + - ${{ each step in parameters.steps }}: + - ${{ step }} + + - ${{ if eq(parameters.enableRichCodeNavigation, true) }}: + - task: RichCodeNavIndexer@0 + displayName: RichCodeNav Upload + inputs: + languages: ${{ coalesce(parameters.richCodeNavigationLanguage, 'csharp') }} + environment: ${{ coalesce(parameters.richCodeNavigationEnvironment, 'production') }} + richNavLogOutputDirectory: $(Build.SourcesDirectory)/artifacts/bin + uploadRichNavArtifacts: ${{ coalesce(parameters.richCodeNavigationUploadArtifacts, false) }} + continueOnError: true + + - template: /eng/common/templates/steps/component-governance.yml + parameters: + ${{ if eq(parameters.disableComponentGovernance, '') }}: + ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.runAsPublic, 'false'), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/dotnet/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/microsoft/'), eq(variables['Build.SourceBranch'], 'refs/heads/main'))) }}: + disableComponentGovernance: false + ${{ else }}: + disableComponentGovernance: true + ${{ else }}: + disableComponentGovernance: ${{ parameters.disableComponentGovernance }} + componentGovernanceIgnoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} + + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: MicroBuildCleanup@1 + displayName: Execute Microbuild cleanup tasks + condition: and(always(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + env: + TeamName: $(_TeamName) + + - ${{ if ne(parameters.artifacts.publish, '') }}: + - ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}: + - task: CopyFiles@2 + displayName: Gather binaries for publish to artifacts + inputs: + SourceFolder: 'artifacts/bin' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/bin' + - task: CopyFiles@2 + displayName: Gather packages for publish to artifacts + inputs: + SourceFolder: 'artifacts/packages' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/packages' + - task: PublishBuildArtifacts@1 + displayName: Publish pipeline artifacts + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts' + PublishLocation: Container + ArtifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} + continueOnError: true + condition: always() + - ${{ if and(ne(parameters.artifacts.publish.logs, 'false'), ne(parameters.artifacts.publish.logs, '')) }}: + - publish: artifacts/log + artifact: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)') }} + displayName: Publish logs + continueOnError: true + condition: always() + + - ${{ if ne(parameters.enablePublishBuildArtifacts, 'false') }}: + - task: PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)' + PublishLocation: Container + ArtifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} + continueOnError: true + condition: always() + + - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'xunit')) }}: + - task: PublishTestResults@2 + displayName: Publish XUnit Test Results + inputs: + testResultsFormat: 'xUnit' + testResultsFiles: '*.xml' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-xunit + mergeTestResults: ${{ parameters.mergeTestResults }} + continueOnError: true + condition: always() + - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'vstest')) }}: + - task: PublishTestResults@2 + displayName: Publish TRX Test Results + inputs: + testResultsFormat: 'VSTest' + testResultsFiles: '*.trx' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-trx + mergeTestResults: ${{ parameters.mergeTestResults }} + continueOnError: true + condition: always() + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.enableSbom, 'true')) }}: + - template: /eng/common/templates/steps/generate-sbom.yml + parameters: + PackageVersion: ${{ parameters.packageVersion}} + BuildDropPath: ${{ parameters.buildDropPath }} + IgnoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} + + - ${{ if eq(parameters.enableBuildRetry, 'true') }}: + - publish: $(Build.SourcesDirectory)\eng\common\BuildConfiguration + artifact: BuildConfiguration + displayName: Publish build retry configuration + continueOnError: true diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/job/onelocbuild.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/job/onelocbuild.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/job/onelocbuild.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/job/onelocbuild.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,109 @@ +parameters: + # Optional: dependencies of the job + dependsOn: '' + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: '' + + CeapexPat: $(dn-bot-ceapex-package-r) # PAT for the loc AzDO instance https://dev.azure.com/ceapex + GithubPat: $(BotAccount-dotnet-bot-repo-PAT) + + SourcesDirectory: $(Build.SourcesDirectory) + CreatePr: true + AutoCompletePr: false + ReusePr: true + UseLfLineEndings: true + UseCheckedInLocProjectJson: false + SkipLocProjectJsonGeneration: false + LanguageSet: VS_Main_Languages + LclSource: lclFilesInRepo + LclPackageId: '' + RepoType: gitHub + GitHubOrg: dotnet + MirrorRepo: '' + MirrorBranch: main + condition: '' + JobNameSuffix: '' + +jobs: +- job: OneLocBuild${{ parameters.JobNameSuffix }} + + dependsOn: ${{ parameters.dependsOn }} + + displayName: OneLocBuild${{ parameters.JobNameSuffix }} + + variables: + - group: OneLocBuildVariables # Contains the CeapexPat and GithubPat + - name: _GenerateLocProjectArguments + value: -SourcesDirectory ${{ parameters.SourcesDirectory }} + -LanguageSet "${{ parameters.LanguageSet }}" + -CreateNeutralXlfs + - ${{ if eq(parameters.UseCheckedInLocProjectJson, 'true') }}: + - name: _GenerateLocProjectArguments + value: ${{ variables._GenerateLocProjectArguments }} -UseCheckedInLocProjectJson + - template: /eng/common/templates/variables/pool-providers.yml + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + ${{ if eq(parameters.pool, '') }}: + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + + steps: + - ${{ if ne(parameters.SkipLocProjectJsonGeneration, 'true') }}: + - task: Powershell@2 + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/generate-locproject.ps1 + arguments: $(_GenerateLocProjectArguments) + displayName: Generate LocProject.json + condition: ${{ parameters.condition }} + + - task: OneLocBuild@2 + displayName: OneLocBuild + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + inputs: + locProj: eng/Localize/LocProject.json + outDir: $(Build.ArtifactStagingDirectory) + lclSource: ${{ parameters.LclSource }} + lclPackageId: ${{ parameters.LclPackageId }} + isCreatePrSelected: ${{ parameters.CreatePr }} + isAutoCompletePrSelected: ${{ parameters.AutoCompletePr }} + ${{ if eq(parameters.CreatePr, true) }}: + isUseLfLineEndingsSelected: ${{ parameters.UseLfLineEndings }} + ${{ if eq(parameters.RepoType, 'gitHub') }}: + isShouldReusePrSelected: ${{ parameters.ReusePr }} + packageSourceAuth: patAuth + patVariable: ${{ parameters.CeapexPat }} + ${{ if eq(parameters.RepoType, 'gitHub') }}: + repoType: ${{ parameters.RepoType }} + gitHubPatVariable: "${{ parameters.GithubPat }}" + ${{ if ne(parameters.MirrorRepo, '') }}: + isMirrorRepoSelected: true + gitHubOrganization: ${{ parameters.GitHubOrg }} + mirrorRepo: ${{ parameters.MirrorRepo }} + mirrorBranch: ${{ parameters.MirrorBranch }} + condition: ${{ parameters.condition }} + + - task: PublishBuildArtifacts@1 + displayName: Publish Localization Files + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/loc' + PublishLocation: Container + ArtifactName: Loc + condition: ${{ parameters.condition }} + + - task: PublishBuildArtifacts@1 + displayName: Publish LocProject.json + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/eng/Localize/' + PublishLocation: Container + ArtifactName: Loc + condition: ${{ parameters.condition }} \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/job/publish-build-assets.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/job/publish-build-assets.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/job/publish-build-assets.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/job/publish-build-assets.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,151 @@ +parameters: + configuration: 'Debug' + + # Optional: condition for the job to run + condition: '' + + # Optional: 'true' if future jobs should run even if this job fails + continueOnError: false + + # Optional: dependencies of the job + dependsOn: '' + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: {} + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing + publishUsingPipelines: false + + # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing + publishAssetsImmediately: false + + artifactsPublishingAdditionalParameters: '' + + signingValidationAdditionalParameters: '' + +jobs: +- job: Asset_Registry_Publish + + dependsOn: ${{ parameters.dependsOn }} + timeoutInMinutes: 150 + + ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + displayName: Publish Assets + ${{ else }}: + displayName: Publish to Build Asset Registry + + variables: + - template: /eng/common/templates/variables/pool-providers.yml + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: Publish-Build-Assets + - group: AzureDevOps-Artifact-Feeds-Pats + - name: runCodesignValidationInjection + value: false + - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + - template: /eng/common/templates/post-build/common-variables.yml + + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + + steps: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download artifact + inputs: + artifactName: AssetManifests + downloadPath: '$(Build.StagingDirectory)/Download' + checkDownloadedFiles: true + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + + - task: NuGetAuthenticate@0 + + - task: PowerShell@2 + displayName: Publish Build Assets + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task PublishBuildAssets -restore -msbuildEngine dotnet + /p:ManifestsPath='$(Build.StagingDirectory)/Download/AssetManifests' + /p:BuildAssetRegistryToken=$(MaestroAccessToken) + /p:MaestroApiEndpoint=https://maestro-prod.westus2.cloudapp.azure.com + /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} + /p:OfficialBuildId=$(Build.BuildNumber) + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + + - task: powershell@2 + displayName: Create ReleaseConfigs Artifact + inputs: + targetType: inline + script: | + Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value $(BARBuildId) + Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value "$(DefaultChannels)" + Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value $(IsStableBuild) + + - task: PublishBuildArtifacts@1 + displayName: Publish ReleaseConfigs Artifact + inputs: + PathtoPublish: '$(Build.StagingDirectory)/ReleaseConfigs.txt' + PublishLocation: Container + ArtifactName: ReleaseConfigs + + - task: powershell@2 + displayName: Check if SymbolPublishingExclusionsFile.txt exists + inputs: + targetType: inline + script: | + $symbolExclusionfile = "$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt" + if(Test-Path -Path $symbolExclusionfile) + { + Write-Host "SymbolExclusionFile exists" + Write-Host "##vso[task.setvariable variable=SymbolExclusionFile]true" + } + else{ + Write-Host "Symbols Exclusion file does not exists" + Write-Host "##vso[task.setvariable variable=SymbolExclusionFile]false" + } + + - task: PublishBuildArtifacts@1 + displayName: Publish SymbolPublishingExclusionsFile Artifact + condition: eq(variables['SymbolExclusionFile'], 'true') + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt' + PublishLocation: Container + ArtifactName: ReleaseConfigs + + - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + - template: /eng/common/templates/post-build/setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: PowerShell@2 + displayName: Publish Using Darc + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 + arguments: -BuildId $(BARBuildId) + -PublishingInfraVersion 3 + -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' + -MaestroToken '$(MaestroApiAccessToken)' + -WaitPublishingFinish true + -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' + -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' + + - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: + - template: /eng/common/templates/steps/publish-logs.yml + parameters: + JobLabel: 'Publish_Artifacts_Logs' diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/job/source-build.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/job/source-build.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/job/source-build.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/job/source-build.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,66 @@ +parameters: + # This template adds arcade-powered source-build to CI. The template produces a server job with a + # default ID 'Source_Build_Complete' to put in a dependency list if necessary. + + # Specifies the prefix for source-build jobs added to pipeline. Use this if disambiguation needed. + jobNamePrefix: 'Source_Build' + + # Defines the platform on which to run the job. By default, a linux-x64 machine, suitable for + # managed-only repositories. This is an object with these properties: + # + # name: '' + # The name of the job. This is included in the job ID. + # targetRID: '' + # The name of the target RID to use, instead of the one auto-detected by Arcade. + # nonPortable: false + # Enables non-portable mode. This means a more specific RID (e.g. fedora.32-x64 rather than + # linux-x64), and compiling against distro-provided packages rather than portable ones. + # skipPublishValidation: false + # Disables publishing validation. By default, a check is performed to ensure no packages are + # published by source-build. + # container: '' + # A container to use. Runs in docker. + # pool: {} + # A pool to use. Runs directly on an agent. + # buildScript: '' + # Specifies the build script to invoke to perform the build in the repo. The default + # './build.sh' should work for typical Arcade repositories, but this is customizable for + # difficult situations. + # jobProperties: {} + # A list of job properties to inject at the top level, for potential extensibility beyond + # container and pool. + platform: {} + +jobs: +- job: ${{ parameters.jobNamePrefix }}_${{ parameters.platform.name }} + displayName: Source-Build (${{ parameters.platform.name }}) + + ${{ each property in parameters.platform.jobProperties }}: + ${{ property.key }}: ${{ property.value }} + + ${{ if ne(parameters.platform.container, '') }}: + container: ${{ parameters.platform.container }} + + ${{ if eq(parameters.platform.pool, '') }}: + # The default VM host AzDO pool. This should be capable of running Docker containers: almost all + # source-build builds run in Docker, including the default managed platform. + # /eng/common/templates/variables/pool-providers.yml can't be used here (some customers declare variables already), so duplicate its logic + pool: + ${{ if eq(variables['System.TeamProject'], 'public') }}: + name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore-Svc-Public' ), False, 'NetCore-Public')] + demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open + + ${{ if eq(variables['System.TeamProject'], 'internal') }}: + name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] + demands: ImageOverride -equals Build.Ubuntu.1804.Amd64 + + ${{ if ne(parameters.platform.pool, '') }}: + pool: ${{ parameters.platform.pool }} + + workspace: + clean: all + + steps: + - template: /eng/common/templates/steps/source-build.yml + parameters: + platform: ${{ parameters.platform }} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/job/source-index-stage1.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/job/source-index-stage1.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/job/source-index-stage1.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/job/source-index-stage1.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,67 @@ +parameters: + runAsPublic: false + sourceIndexPackageVersion: 1.0.1-20230228.2 + sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json + sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci" + preSteps: [] + binlogPath: artifacts/log/Debug/Build.binlog + condition: '' + dependsOn: '' + pool: '' + +jobs: +- job: SourceIndexStage1 + dependsOn: ${{ parameters.dependsOn }} + condition: ${{ parameters.condition }} + variables: + - name: SourceIndexPackageVersion + value: ${{ parameters.sourceIndexPackageVersion }} + - name: SourceIndexPackageSource + value: ${{ parameters.sourceIndexPackageSource }} + - name: BinlogPath + value: ${{ parameters.binlogPath }} + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: source-dot-net stage1 variables + - template: /eng/common/templates/variables/pool-providers.yml + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + ${{ if eq(parameters.pool, '') }}: + pool: + ${{ if eq(variables['System.TeamProject'], 'public') }}: + name: $(DncEngPublicBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64.open + ${{ if eq(variables['System.TeamProject'], 'internal') }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + + steps: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} + + - task: UseDotNet@2 + displayName: Use .NET Core SDK 6 + inputs: + packageType: sdk + version: 6.0.x + installationPath: $(Agent.TempDirectory)/dotnet + workingDirectory: $(Agent.TempDirectory) + + - script: | + $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version $(SourceIndexPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version $(SourceIndexPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools + displayName: Download Tools + # Set working directory to temp directory so 'dotnet' doesn't try to use global.json and use the repo's sdk. + workingDirectory: $(Agent.TempDirectory) + + - script: ${{ parameters.sourceIndexBuildCommand }} + displayName: Build Repository + + - script: $(Agent.TempDirectory)/.source-index/tools/BinLogToSln -i $(BinlogPath) -r $(Build.SourcesDirectory) -n $(Build.Repository.Name) -o .source-index/stage1output + displayName: Process Binlog into indexable sln + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - script: $(Agent.TempDirectory)/.source-index/tools/UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) + displayName: Upload stage1 artifacts to source index + env: + BLOB_CONTAINER_URL: $(source-dot-net-stage1-blob-container-url) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/jobs/codeql-build.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/jobs/codeql-build.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/jobs/codeql-build.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/jobs/codeql-build.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,31 @@ +parameters: + # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md + continueOnError: false + # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + jobs: [] + # Optional: if specified, restore and use this version of Guardian instead of the default. + overrideGuardianVersion: '' + +jobs: +- template: /eng/common/templates/jobs/jobs.yml + parameters: + enableMicrobuild: false + enablePublishBuildArtifacts: false + enablePublishTestResults: false + enablePublishBuildAssets: false + enablePublishUsingPipelines: false + enableTelemetry: true + + variables: + - group: Publish-Build-Assets + # The Guardian version specified in 'eng/common/sdl/packages.config'. This value must be kept in + # sync with the packages.config file. + - name: DefaultGuardianVersion + value: 0.109.0 + - name: GuardianPackagesConfigFile + value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config + - name: GuardianVersion + value: ${{ coalesce(parameters.overrideGuardianVersion, '$(DefaultGuardianVersion)') }} + + jobs: ${{ parameters.jobs }} + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/jobs/jobs.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/jobs/jobs.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/jobs/jobs.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/jobs/jobs.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,97 @@ +parameters: + # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md + continueOnError: false + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: Enable publishing using release pipelines + enablePublishUsingPipelines: false + + # Optional: Enable running the source-build jobs to build repo from source + enableSourceBuild: false + + # Optional: Parameters for source-build template. + # See /eng/common/templates/jobs/source-build.yml for options + sourceBuildParameters: [] + + graphFileGeneration: + # Optional: Enable generating the graph files at the end of the build + enabled: false + # Optional: Include toolset dependencies in the generated graph files + includeToolset: false + + # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + jobs: [] + + # Optional: Override automatically derived dependsOn value for "publish build assets" job + publishBuildAssetsDependsOn: '' + + # Optional: Publish the assets as soon as the publish to BAR stage is complete, rather doing so in a separate stage. + publishAssetsImmediately: false + + # Optional: If using publishAssetsImmediately and additional parameters are needed, can be used to send along additional parameters (normally sent to post-build.yml) + artifactsPublishingAdditionalParameters: '' + signingValidationAdditionalParameters: '' + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + enableSourceIndex: false + sourceIndexParams: {} + +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +jobs: +- ${{ each job in parameters.jobs }}: + - template: ../job/job.yml + parameters: + # pass along parameters + ${{ each parameter in parameters }}: + ${{ if ne(parameter.key, 'jobs') }}: + ${{ parameter.key }}: ${{ parameter.value }} + + # pass along job properties + ${{ each property in job }}: + ${{ if ne(property.key, 'job') }}: + ${{ property.key }}: ${{ property.value }} + + name: ${{ job.job }} + +- ${{ if eq(parameters.enableSourceBuild, true) }}: + - template: /eng/common/templates/jobs/source-build.yml + parameters: + allCompletedJobId: Source_Build_Complete + ${{ each parameter in parameters.sourceBuildParameters }}: + ${{ parameter.key }}: ${{ parameter.value }} + +- ${{ if eq(parameters.enableSourceIndex, 'true') }}: + - template: ../job/source-index-stage1.yml + parameters: + runAsPublic: ${{ parameters.runAsPublic }} + ${{ each parameter in parameters.sourceIndexParams }}: + ${{ parameter.key }}: ${{ parameter.value }} + +- ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if or(eq(parameters.enablePublishBuildAssets, true), eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: + - template: ../job/publish-build-assets.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + dependsOn: + - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.publishBuildAssetsDependsOn }}: + - ${{ job.job }} + - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.jobs }}: + - ${{ job.job }} + - ${{ if eq(parameters.enableSourceBuild, true) }}: + - Source_Build_Complete + + runAsPublic: ${{ parameters.runAsPublic }} + publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} + publishAssetsImmediately: ${{ parameters.publishAssetsImmediately }} + enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + signingValidationAdditionalParameters: ${{ parameters.signingValidationAdditionalParameters }} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/jobs/source-build.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/jobs/source-build.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/jobs/source-build.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/jobs/source-build.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,46 @@ +parameters: + # This template adds arcade-powered source-build to CI. A job is created for each platform, as + # well as an optional server job that completes when all platform jobs complete. + + # The name of the "join" job for all source-build platforms. If set to empty string, the job is + # not included. Existing repo pipelines can use this job depend on all source-build jobs + # completing without maintaining a separate list of every single job ID: just depend on this one + # server job. By default, not included. Recommended name if used: 'Source_Build_Complete'. + allCompletedJobId: '' + + # See /eng/common/templates/job/source-build.yml + jobNamePrefix: 'Source_Build' + + # This is the default platform provided by Arcade, intended for use by a managed-only repo. + defaultManagedPlatform: + name: 'Managed' + container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream8' + + # Defines the platforms on which to run build jobs. One job is created for each platform, and the + # object in this array is sent to the job template as 'platform'. If no platforms are specified, + # one job runs on 'defaultManagedPlatform'. + platforms: [] + +jobs: + +- ${{ if ne(parameters.allCompletedJobId, '') }}: + - job: ${{ parameters.allCompletedJobId }} + displayName: Source-Build Complete + pool: server + dependsOn: + - ${{ each platform in parameters.platforms }}: + - ${{ parameters.jobNamePrefix }}_${{ platform.name }} + - ${{ if eq(length(parameters.platforms), 0) }}: + - ${{ parameters.jobNamePrefix }}_${{ parameters.defaultManagedPlatform.name }} + +- ${{ each platform in parameters.platforms }}: + - template: /eng/common/templates/job/source-build.yml + parameters: + jobNamePrefix: ${{ parameters.jobNamePrefix }} + platform: ${{ platform }} + +- ${{ if eq(length(parameters.platforms), 0) }}: + - template: /eng/common/templates/job/source-build.yml + parameters: + jobNamePrefix: ${{ parameters.jobNamePrefix }} + platform: ${{ parameters.defaultManagedPlatform }} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/post-build/common-variables.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/post-build/common-variables.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/post-build/common-variables.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/post-build/common-variables.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,22 @@ +variables: + - group: Publish-Build-Assets + + # Whether the build is internal or not + - name: IsInternalBuild + value: ${{ and(ne(variables['System.TeamProject'], 'public'), contains(variables['Build.SourceBranch'], 'internal')) }} + + # Default Maestro++ API Endpoint and API Version + - name: MaestroApiEndPoint + value: "https://maestro-prod.westus2.cloudapp.azure.com" + - name: MaestroApiAccessToken + value: $(MaestroAccessToken) + - name: MaestroApiVersion + value: "2020-02-20" + + - name: SourceLinkCLIVersion + value: 3.0.0 + - name: SymbolToolVersion + value: 1.0.1 + + - name: runCodesignValidationInjection + value: false diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/post-build/post-build.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/post-build/post-build.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/post-build/post-build.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/post-build/post-build.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,281 @@ +parameters: + # Which publishing infra should be used. THIS SHOULD MATCH THE VERSION ON THE BUILD MANIFEST. + # Publishing V1 is no longer supported + # Publishing V2 is no longer supported + # Publishing V3 is the default + - name: publishingInfraVersion + displayName: Which version of publishing should be used to promote the build definition? + type: number + default: 3 + values: + - 3 + + - name: BARBuildId + displayName: BAR Build Id + type: number + default: 0 + + - name: PromoteToChannelIds + displayName: Channel to promote BARBuildId to + type: string + default: '' + + - name: enableSourceLinkValidation + displayName: Enable SourceLink validation + type: boolean + default: false + + - name: enableSigningValidation + displayName: Enable signing validation + type: boolean + default: true + + - name: enableSymbolValidation + displayName: Enable symbol validation + type: boolean + default: false + + - name: enableNugetValidation + displayName: Enable NuGet validation + type: boolean + default: true + + - name: publishInstallersAndChecksums + displayName: Publish installers and checksums + type: boolean + default: true + + - name: SDLValidationParameters + type: object + default: + enable: false + publishGdn: false + continueOnError: false + params: '' + artifactNames: '' + downloadArtifacts: true + + # These parameters let the user customize the call to sdk-task.ps1 for publishing + # symbols & general artifacts as well as for signing validation + - name: symbolPublishingAdditionalParameters + displayName: Symbol publishing additional parameters + type: string + default: '' + + - name: artifactsPublishingAdditionalParameters + displayName: Artifact publishing additional parameters + type: string + default: '' + + - name: signingValidationAdditionalParameters + displayName: Signing validation additional parameters + type: string + default: '' + + # Which stages should finish execution before post-build stages start + - name: validateDependsOn + type: object + default: + - build + + - name: publishDependsOn + type: object + default: + - Validate + + # Optional: Call asset publishing rather than running in a separate stage + - name: publishAssetsImmediately + type: boolean + default: false + +stages: +- ${{ if or(eq( parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + - stage: Validate + dependsOn: ${{ parameters.validateDependsOn }} + displayName: Validate Build Assets + variables: + - template: common-variables.yml + - template: /eng/common/templates/variables/pool-providers.yml + jobs: + - job: + displayName: NuGet Validation + condition: and(succeededOrFailed(), eq( ${{ parameters.enableNugetValidation }}, 'true')) + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/nuget-validation.ps1 + arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ + -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ + + - job: + displayName: Signing Validation + condition: and( eq( ${{ parameters.enableSigningValidation }}, 'true'), ne( variables['PostBuildSign'], 'true')) + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + itemPattern: | + ** + !**/Microsoft.SourceBuild.Intermediate.*.nupkg + + # This is necessary whenever we want to publish/restore to an AzDO private feed + # Since sdk-task.ps1 tries to restore packages we need to do this authentication here + # otherwise it'll complain about accessing a private feed. + - task: NuGetAuthenticate@0 + displayName: 'Authenticate to AzDO Feeds' + + # Signing validation will optionally work with the buildmanifest file which is downloaded from + # Azure DevOps above. + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task SigningValidation -restore -msbuildEngine vs + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' + /p:SignCheckExclusionsFile='$(Build.SourcesDirectory)/eng/SignCheckExclusionsFile.txt' + ${{ parameters.signingValidationAdditionalParameters }} + + - template: ../steps/publish-logs.yml + parameters: + StageLabel: 'Validation' + JobLabel: 'Signing' + + - job: + displayName: SourceLink Validation + condition: eq( ${{ parameters.enableSourceLinkValidation }}, 'true') + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: BlobArtifacts + checkDownloadedFiles: true + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 + arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ + -ExtractPath $(Agent.BuildDirectory)/Extract/ + -GHRepoName $(Build.Repository.Name) + -GHCommit $(Build.SourceVersion) + -SourcelinkCliVersion $(SourceLinkCLIVersion) + continueOnError: true + + - template: /eng/common/templates/job/execute-sdl.yml + parameters: + enable: ${{ parameters.SDLValidationParameters.enable }} + publishGuardianDirectoryToPipeline: ${{ parameters.SDLValidationParameters.publishGdn }} + additionalParameters: ${{ parameters.SDLValidationParameters.params }} + continueOnError: ${{ parameters.SDLValidationParameters.continueOnError }} + artifactNames: ${{ parameters.SDLValidationParameters.artifactNames }} + downloadArtifacts: ${{ parameters.SDLValidationParameters.downloadArtifacts }} + +- ${{ if ne(parameters.publishAssetsImmediately, 'true') }}: + - stage: publish_using_darc + ${{ if or(eq(parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + dependsOn: ${{ parameters.publishDependsOn }} + ${{ else }}: + dependsOn: ${{ parameters.validateDependsOn }} + displayName: Publish using Darc + variables: + - template: common-variables.yml + - template: /eng/common/templates/variables/pool-providers.yml + jobs: + - job: + displayName: Publish Using Darc + timeoutInMinutes: 120 + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: NuGetAuthenticate@0 + + - task: PowerShell@2 + displayName: Publish Using Darc + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 + arguments: -BuildId $(BARBuildId) + -PublishingInfraVersion ${{ parameters.publishingInfraVersion }} + -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' + -MaestroToken '$(MaestroApiAccessToken)' + -WaitPublishingFinish true + -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' + -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/post-build/setup-maestro-vars.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/post-build/setup-maestro-vars.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/post-build/setup-maestro-vars.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/post-build/setup-maestro-vars.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,70 @@ +parameters: + BARBuildId: '' + PromoteToChannelIds: '' + +steps: + - ${{ if eq(coalesce(parameters.PromoteToChannelIds, 0), 0) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Release Configs + inputs: + buildType: current + artifactName: ReleaseConfigs + checkDownloadedFiles: true + + - task: PowerShell@2 + name: setReleaseVars + displayName: Set Release Configs Vars + inputs: + targetType: inline + pwsh: true + script: | + try { + if (!$Env:PromoteToMaestroChannels -or $Env:PromoteToMaestroChannels.Trim() -eq '') { + $Content = Get-Content $(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt + + $BarId = $Content | Select -Index 0 + $Channels = $Content | Select -Index 1 + $IsStableBuild = $Content | Select -Index 2 + + $AzureDevOpsProject = $Env:System_TeamProject + $AzureDevOpsBuildDefinitionId = $Env:System_DefinitionId + $AzureDevOpsBuildId = $Env:Build_BuildId + } + else { + $buildApiEndpoint = "${Env:MaestroApiEndPoint}/api/builds/${Env:BARBuildId}?api-version=${Env:MaestroApiVersion}" + + $apiHeaders = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' + $apiHeaders.Add('Accept', 'application/json') + $apiHeaders.Add('Authorization',"Bearer ${Env:MAESTRO_API_TOKEN}") + + $buildInfo = try { Invoke-WebRequest -Method Get -Uri $buildApiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + + $BarId = $Env:BARBuildId + $Channels = $Env:PromoteToMaestroChannels -split "," + $Channels = $Channels -join "][" + $Channels = "[$Channels]" + + $IsStableBuild = $buildInfo.stable + $AzureDevOpsProject = $buildInfo.azureDevOpsProject + $AzureDevOpsBuildDefinitionId = $buildInfo.azureDevOpsBuildDefinitionId + $AzureDevOpsBuildId = $buildInfo.azureDevOpsBuildId + } + + Write-Host "##vso[task.setvariable variable=BARBuildId]$BarId" + Write-Host "##vso[task.setvariable variable=TargetChannels]$Channels" + Write-Host "##vso[task.setvariable variable=IsStableBuild]$IsStableBuild" + + Write-Host "##vso[task.setvariable variable=AzDOProjectName]$AzureDevOpsProject" + Write-Host "##vso[task.setvariable variable=AzDOPipelineId]$AzureDevOpsBuildDefinitionId" + Write-Host "##vso[task.setvariable variable=AzDOBuildId]$AzureDevOpsBuildId" + } + catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + exit 1 + } + env: + MAESTRO_API_TOKEN: $(MaestroApiAccessToken) + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToMaestroChannels: ${{ parameters.PromoteToChannelIds }} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/post-build/trigger-subscription.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/post-build/trigger-subscription.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/post-build/trigger-subscription.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/post-build/trigger-subscription.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,13 @@ +parameters: + ChannelId: 0 + +steps: +- task: PowerShell@2 + displayName: Triggering subscriptions + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/trigger-subscriptions.ps1 + arguments: -SourceRepo $(Build.Repository.Uri) + -ChannelId ${{ parameters.ChannelId }} + -MaestroApiAccessToken $(MaestroAccessToken) + -MaestroApiEndPoint $(MaestroApiEndPoint) + -MaestroApiVersion $(MaestroApiVersion) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/add-build-to-channel.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/add-build-to-channel.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/add-build-to-channel.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/add-build-to-channel.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,13 @@ +parameters: + ChannelId: 0 + +steps: +- task: PowerShell@2 + displayName: Add Build to Channel + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/add-build-to-channel.ps1 + arguments: -BuildId $(BARBuildId) + -ChannelId ${{ parameters.ChannelId }} + -MaestroApiAccessToken $(MaestroApiAccessToken) + -MaestroApiEndPoint $(MaestroApiEndPoint) + -MaestroApiVersion $(MaestroApiVersion) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/build-reason.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/build-reason.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/build-reason.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/build-reason.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,12 @@ +# build-reason.yml +# Description: runs steps if build.reason condition is valid. conditions is a string of valid build reasons +# to include steps (',' separated). +parameters: + conditions: '' + steps: [] + +steps: + - ${{ if and( not(startsWith(parameters.conditions, 'not')), contains(parameters.conditions, variables['build.reason'])) }}: + - ${{ parameters.steps }} + - ${{ if and( startsWith(parameters.conditions, 'not'), not(contains(parameters.conditions, variables['build.reason']))) }}: + - ${{ parameters.steps }} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/component-governance.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/component-governance.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/component-governance.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/component-governance.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,13 @@ +parameters: + disableComponentGovernance: false + componentGovernanceIgnoreDirectories: '' + +steps: +- ${{ if eq(parameters.disableComponentGovernance, 'true') }}: + - script: "echo ##vso[task.setvariable variable=skipComponentGovernanceDetection]true" + displayName: Set skipComponentGovernanceDetection variable +- ${{ if ne(parameters.disableComponentGovernance, 'true') }}: + - task: ComponentGovernanceComponentDetection@0 + continueOnError: true + inputs: + ignoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/execute-codeql.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/execute-codeql.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/execute-codeql.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/execute-codeql.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,32 @@ +parameters: + # Language that should be analyzed. Defaults to csharp + language: csharp + # Build Commands + buildCommands: '' + overrideParameters: '' # Optional: to override values for parameters. + additionalParameters: '' # Optional: parameters that need user specific values eg: '-SourceToolsList @("abc","def") -ArtifactToolsList @("ghi","jkl")' + # Optional: if specified, restore and use this version of Guardian instead of the default. + overrideGuardianVersion: '' + # Optional: if true, publish the '.gdn' folder as a pipeline artifact. This can help with in-depth + # diagnosis of problems with specific tool configurations. + publishGuardianDirectoryToPipeline: false + # The script to run to execute all SDL tools. Use this if you want to use a script to define SDL + # parameters rather than relying on YAML. It may be better to use a local script, because you can + # reproduce results locally without piecing together a command based on the YAML. + executeAllSdlToolsScript: 'eng/common/sdl/execute-all-sdl-tools.ps1' + # There is some sort of bug (has been reported) in Azure DevOps where if this parameter is named + # 'continueOnError', the parameter value is not correctly picked up. + # This can also be remedied by the caller (post-build.yml) if it does not use a nested parameter + # optional: determines whether to continue the build if the step errors; + sdlContinueOnError: false + +steps: +- template: /eng/common/templates/steps/execute-sdl.yml + parameters: + overrideGuardianVersion: ${{ parameters.overrideGuardianVersion }} + executeAllSdlToolsScript: ${{ parameters.executeAllSdlToolsScript }} + overrideParameters: ${{ parameters.overrideParameters }} + additionalParameters: '${{ parameters.additionalParameters }} + -CodeQLAdditionalRunConfigParams @("BuildCommands < ${{ parameters.buildCommands }}", "Language < ${{ parameters.language }}")' + publishGuardianDirectoryToPipeline: ${{ parameters.publishGuardianDirectoryToPipeline }} + sdlContinueOnError: ${{ parameters.sdlContinueOnError }} \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/execute-sdl.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/execute-sdl.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/execute-sdl.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/execute-sdl.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,88 @@ +parameters: + overrideGuardianVersion: '' + executeAllSdlToolsScript: '' + overrideParameters: '' + additionalParameters: '' + publishGuardianDirectoryToPipeline: false + sdlContinueOnError: false + condition: '' + +steps: +- task: NuGetAuthenticate@1 + inputs: + nuGetServiceConnections: GuardianConnect + +- task: NuGetToolInstaller@1 + displayName: 'Install NuGet.exe' + +- ${{ if ne(parameters.overrideGuardianVersion, '') }}: + - pwsh: | + Set-Location -Path $(Build.SourcesDirectory)\eng\common\sdl + . .\sdl.ps1 + $guardianCliLocation = Install-Gdn -Path $(Build.SourcesDirectory)\.artifacts -Version ${{ parameters.overrideGuardianVersion }} + Write-Host "##vso[task.setvariable variable=GuardianCliLocation]$guardianCliLocation" + displayName: Install Guardian (Overridden) + +- ${{ if eq(parameters.overrideGuardianVersion, '') }}: + - pwsh: | + Set-Location -Path $(Build.SourcesDirectory)\eng\common\sdl + . .\sdl.ps1 + $guardianCliLocation = Install-Gdn -Path $(Build.SourcesDirectory)\.artifacts + Write-Host "##vso[task.setvariable variable=GuardianCliLocation]$guardianCliLocation" + displayName: Install Guardian + +- ${{ if ne(parameters.overrideParameters, '') }}: + - powershell: ${{ parameters.executeAllSdlToolsScript }} ${{ parameters.overrideParameters }} + displayName: Execute SDL (Overridden) + continueOnError: ${{ parameters.sdlContinueOnError }} + condition: ${{ parameters.condition }} + +- ${{ if eq(parameters.overrideParameters, '') }}: + - powershell: ${{ parameters.executeAllSdlToolsScript }} + -GuardianCliLocation $(GuardianCliLocation) + -NugetPackageDirectory $(Build.SourcesDirectory)\.packages + -AzureDevOpsAccessToken $(dn-bot-dotnet-build-rw-code-rw) + ${{ parameters.additionalParameters }} + displayName: Execute SDL + continueOnError: ${{ parameters.sdlContinueOnError }} + condition: ${{ parameters.condition }} + +- ${{ if ne(parameters.publishGuardianDirectoryToPipeline, 'false') }}: + # We want to publish the Guardian results and configuration for easy diagnosis. However, the + # '.gdn' dir is a mix of configuration, results, extracted dependencies, and Guardian default + # tooling files. Some of these files are large and aren't useful during an investigation, so + # exclude them by simply deleting them before publishing. (As of writing, there is no documented + # way to selectively exclude a dir from the pipeline artifact publish task.) + - task: DeleteFiles@1 + displayName: Delete Guardian dependencies to avoid uploading + inputs: + SourceFolder: $(Agent.BuildDirectory)/.gdn + Contents: | + c + i + condition: succeededOrFailed() + + - publish: $(Agent.BuildDirectory)/.gdn + artifact: GuardianConfiguration + displayName: Publish GuardianConfiguration + condition: succeededOrFailed() + + # Publish the SARIF files in a container named CodeAnalysisLogs to enable integration + # with the "SARIF SAST Scans Tab" Azure DevOps extension + - task: CopyFiles@2 + displayName: Copy SARIF files + inputs: + flattenFolders: true + sourceFolder: $(Agent.BuildDirectory)/.gdn/rc/ + contents: '**/*.sarif' + targetFolder: $(Build.SourcesDirectory)/CodeAnalysisLogs + condition: succeededOrFailed() + + # Use PublishBuildArtifacts because the SARIF extension only checks this case + # see microsoft/sarif-azuredevops-extension#4 + - task: PublishBuildArtifacts@1 + displayName: Publish SARIF files to CodeAnalysisLogs container + inputs: + pathToPublish: $(Build.SourcesDirectory)/CodeAnalysisLogs + artifactName: CodeAnalysisLogs + condition: succeededOrFailed() \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/generate-sbom.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/generate-sbom.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/generate-sbom.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/generate-sbom.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,48 @@ +# BuildDropPath - The root folder of the drop directory for which the manifest file will be generated. +# PackageName - The name of the package this SBOM represents. +# PackageVersion - The version of the package this SBOM represents. +# ManifestDirPath - The path of the directory where the generated manifest files will be placed +# IgnoreDirectories - Directories to ignore for SBOM generation. This will be passed through to the CG component detector. + +parameters: + PackageVersion: 7.0.0 + BuildDropPath: '$(Build.SourcesDirectory)/artifacts' + PackageName: '.NET' + ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom + IgnoreDirectories: '' + sbomContinueOnError: true + +steps: +- task: PowerShell@2 + displayName: Prep for SBOM generation in (Non-linux) + condition: or(eq(variables['Agent.Os'], 'Windows_NT'), eq(variables['Agent.Os'], 'Darwin')) + inputs: + filePath: ./eng/common/generate-sbom-prep.ps1 + arguments: ${{parameters.manifestDirPath}} + +# Chmodding is a workaround for https://github.com/dotnet/arcade/issues/8461 +- script: | + chmod +x ./eng/common/generate-sbom-prep.sh + ./eng/common/generate-sbom-prep.sh ${{parameters.manifestDirPath}} + displayName: Prep for SBOM generation in (Linux) + condition: eq(variables['Agent.Os'], 'Linux') + continueOnError: ${{ parameters.sbomContinueOnError }} + +- task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: 'Generate SBOM manifest' + continueOnError: ${{ parameters.sbomContinueOnError }} + inputs: + PackageName: ${{ parameters.packageName }} + BuildDropPath: ${{ parameters.buildDropPath }} + PackageVersion: ${{ parameters.packageVersion }} + ManifestDirPath: ${{ parameters.manifestDirPath }} + ${{ if ne(parameters.IgnoreDirectories, '') }}: + AdditionalComponentDetectorArgs: '--IgnoreDirectories ${{ parameters.IgnoreDirectories }}' + +- task: PublishPipelineArtifact@1 + displayName: Publish SBOM manifest + continueOnError: ${{parameters.sbomContinueOnError}} + inputs: + targetPath: '${{parameters.manifestDirPath}}' + artifactName: $(ARTIFACT_NAME) + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/publish-logs.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/publish-logs.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/publish-logs.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/publish-logs.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,23 @@ +parameters: + StageLabel: '' + JobLabel: '' + +steps: +- task: Powershell@2 + displayName: Prepare Binlogs to Upload + inputs: + targetType: inline + script: | + New-Item -ItemType Directory $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + Move-Item -Path $(Build.SourcesDirectory)/artifacts/log/Debug/* $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + continueOnError: true + condition: always() + +- task: PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/PostBuildLogs' + PublishLocation: Container + ArtifactName: PostBuildLogs + continueOnError: true + condition: always() diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/retain-build.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/retain-build.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/retain-build.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/retain-build.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,28 @@ +parameters: + # Optional azure devops PAT with build execute permissions for the build's organization, + # only needed if the build that should be retained ran on a different organization than + # the pipeline where this template is executing from + Token: '' + # Optional BuildId to retain, defaults to the current running build + BuildId: '' + # Azure devops Organization URI for the build in the https://dev.azure.com/ format. + # Defaults to the organization the current pipeline is running on + AzdoOrgUri: '$(System.CollectionUri)' + # Azure devops project for the build. Defaults to the project the current pipeline is running on + AzdoProject: '$(System.TeamProject)' + +steps: + - task: powershell@2 + inputs: + targetType: 'filePath' + filePath: eng/common/retain-build.ps1 + pwsh: true + arguments: > + -AzdoOrgUri: ${{parameters.AzdoOrgUri}} + -AzdoProject ${{parameters.AzdoProject}} + -Token ${{coalesce(parameters.Token, '$env:SYSTEM_ACCESSTOKEN') }} + -BuildId ${{coalesce(parameters.BuildId, '$env:BUILD_ID')}} + displayName: Enable permanent build retention + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + BUILD_ID: $(Build.BuildId) \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/run-on-unix.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/run-on-unix.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/run-on-unix.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/run-on-unix.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,7 @@ +parameters: + agentOs: '' + steps: [] + +steps: +- ${{ if ne(parameters.agentOs, 'Windows_NT') }}: + - ${{ parameters.steps }} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/run-on-windows.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/run-on-windows.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/run-on-windows.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/run-on-windows.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,7 @@ +parameters: + agentOs: '' + steps: [] + +steps: +- ${{ if eq(parameters.agentOs, 'Windows_NT') }}: + - ${{ parameters.steps }} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/run-script-ifequalelse.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/run-script-ifequalelse.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/run-script-ifequalelse.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/run-script-ifequalelse.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,33 @@ +parameters: + # if parameter1 equals parameter 2, run 'ifScript' command, else run 'elsescript' command + parameter1: '' + parameter2: '' + ifScript: '' + elseScript: '' + + # name of script step + name: Script + + # display name of script step + displayName: If-Equal-Else Script + + # environment + env: {} + + # conditional expression for step execution + condition: '' + +steps: +- ${{ if and(ne(parameters.ifScript, ''), eq(parameters.parameter1, parameters.parameter2)) }}: + - script: ${{ parameters.ifScript }} + name: ${{ parameters.name }} + displayName: ${{ parameters.displayName }} + env: ${{ parameters.env }} + condition: ${{ parameters.condition }} + +- ${{ if and(ne(parameters.elseScript, ''), ne(parameters.parameter1, parameters.parameter2)) }}: + - script: ${{ parameters.elseScript }} + name: ${{ parameters.name }} + displayName: ${{ parameters.displayName }} + env: ${{ parameters.env }} + condition: ${{ parameters.condition }} \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/send-to-helix.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/send-to-helix.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/send-to-helix.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/send-to-helix.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,91 @@ +# Please remember to update the documentation if you make changes to these parameters! +parameters: + HelixSource: 'pr/default' # required -- sources must start with pr/, official/, prodcon/, or agent/ + HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/' + HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number + HelixTargetQueues: '' # required -- semicolon-delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues + HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group + HelixConfiguration: '' # optional -- additional property attached to a job + HelixPreCommands: '' # optional -- commands to run before Helix work item execution + HelixPostCommands: '' # optional -- commands to run after Helix work item execution + WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects + WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects + WorkItemTimeout: '' # optional -- a timeout in TimeSpan.Parse-ready value (e.g. 00:02:00) for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects + CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload + XUnitProjects: '' # optional -- semicolon-delimited list of XUnitProjects to parse and send to Helix; requires XUnitRuntimeTargetFramework, XUnitPublishTargetFramework, XUnitRunnerVersion, and IncludeDotNetCli=true + XUnitWorkItemTimeout: '' # optional -- the workitem timeout in seconds for all workitems created from the xUnit projects specified by XUnitProjects + XUnitPublishTargetFramework: '' # optional -- framework to use to publish your xUnit projects + XUnitRuntimeTargetFramework: '' # optional -- framework to use for the xUnit console runner + XUnitRunnerVersion: '' # optional -- version of the xUnit nuget package you wish to use on Helix; required for XUnitProjects + IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion + DotNetCliPackageType: '' # optional -- either 'sdk', 'runtime' or 'aspnetcore-runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json + DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json + WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." + IsExternal: false # [DEPRECATED] -- doesn't do anything, jobs are external if HelixAccessToken is empty and Creator is set + HelixBaseUri: 'https://helix.dot.net/' # optional -- sets the Helix API base URI (allows targeting https://helix.int-dot.net ) + Creator: '' # optional -- if the build is external, use this to specify who is sending the job + DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO + condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() + continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false + +steps: + - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY\eng\common\helixpublish.proj /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"' + displayName: ${{ parameters.DisplayNamePrefix }} (Windows) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixAccessToken: ${{ parameters.HelixAccessToken }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/eng/common/helixpublish.proj /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog + displayName: ${{ parameters.DisplayNamePrefix }} (Unix) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixAccessToken: ${{ parameters.HelixAccessToken }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/source-build.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/source-build.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/source-build.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/source-build.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,131 @@ +parameters: + # This template adds arcade-powered source-build to CI. + + # This is a 'steps' template, and is intended for advanced scenarios where the existing build + # infra has a careful build methodology that must be followed. For example, a repo + # (dotnet/runtime) might choose to clone the GitHub repo only once and store it as a pipeline + # artifact for all subsequent jobs to use, to reduce dependence on a strong network connection to + # GitHub. Using this steps template leaves room for that infra to be included. + + # Defines the platform on which to run the steps. See 'eng/common/templates/job/source-build.yml' + # for details. The entire object is described in the 'job' template for simplicity, even though + # the usage of the properties on this object is split between the 'job' and 'steps' templates. + platform: {} + +steps: +# Build. Keep it self-contained for simple reusability. (No source-build-specific job variables.) +- ${{ if eq(variables['System.TeamProject'], 'internal') }}: + - task: NuGetAuthenticate@0 +- script: | + set -x + df -h + + # If building on the internal project, the artifact feeds variable may be available (usually only if needed) + # In that case, call the feed setup script to add internal feeds corresponding to public ones. + # In addition, add an msbuild argument to copy the WIP from the repo to the target build location. + # This is because SetupNuGetSources.sh will alter the current NuGet.config file, and we need to preserve those + # changes. + internalRestoreArgs= + if [ '$(dn-bot-dnceng-artifact-feeds-rw)' != '$''(dn-bot-dnceng-artifact-feeds-rw)' ]; then + # Temporarily work around https://github.com/dotnet/arcade/issues/7709 + chmod +x $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh $(Build.SourcesDirectory)/NuGet.config $(dn-bot-dnceng-artifact-feeds-rw) + internalRestoreArgs='/p:CopyWipIntoInnerSourceBuildRepo=true' + + # The 'Copy WIP' feature of source build uses git stash to apply changes from the original repo. + # This only works if there is a username/email configured, which won't be the case in most CI runs. + git config --get user.email + if [ $? -ne 0 ]; then + git config user.email dn-bot@microsoft.com + git config user.name dn-bot + fi + fi + + # If building on the internal project, the internal storage variable may be available (usually only if needed) + # In that case, add variables to allow the download of internal runtimes if the specified versions are not found + # in the default public locations. + internalRuntimeDownloadArgs= + if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then + internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://dotnetbuilds.blob.core.windows.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)' + fi + + buildConfig=Release + # Check if AzDO substitutes in a build config from a variable, and use it if so. + if [ '$(_BuildConfig)' != '$''(_BuildConfig)' ]; then + buildConfig='$(_BuildConfig)' + fi + + officialBuildArgs= + if [ '${{ and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}' = 'True' ]; then + officialBuildArgs='/p:DotNetPublishUsingPipelines=true /p:OfficialBuildId=$(BUILD.BUILDNUMBER)' + fi + + targetRidArgs= + if [ '${{ parameters.platform.targetRID }}' != '' ]; then + targetRidArgs='/p:TargetRid=${{ parameters.platform.targetRID }}' + fi + + runtimeOsArgs= + if [ '${{ parameters.platform.runtimeOS }}' != '' ]; then + runtimeOsArgs='/p:RuntimeOS=${{ parameters.platform.runtimeOS }}' + fi + + baseOsArgs= + if [ '${{ parameters.platform.baseOS }}' != '' ]; then + baseOsArgs='/p:BaseOS=${{ parameters.platform.baseOS }}' + fi + + publishArgs= + if [ '${{ parameters.platform.skipPublishValidation }}' != 'true' ]; then + publishArgs='--publish' + fi + + assetManifestFileName=SourceBuild_RidSpecific.xml + if [ '${{ parameters.platform.name }}' != '' ]; then + assetManifestFileName=SourceBuild_${{ parameters.platform.name }}.xml + fi + + ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ + --configuration $buildConfig \ + --restore --build --pack $publishArgs -bl \ + $officialBuildArgs \ + $internalRuntimeDownloadArgs \ + $internalRestoreArgs \ + $targetRidArgs \ + $runtimeOsArgs \ + $baseOsArgs \ + /p:SourceBuildNonPortable=${{ parameters.platform.nonPortable }} \ + /p:ArcadeBuildFromSource=true \ + /p:AssetManifestFileName=$assetManifestFileName + displayName: Build + +# Upload build logs for diagnosis. +- task: CopyFiles@2 + displayName: Prepare BuildLogs staging directory + inputs: + SourceFolder: '$(Build.SourcesDirectory)' + Contents: | + **/*.log + **/*.binlog + artifacts/source-build/self/prebuilt-report/** + TargetFolder: '$(Build.StagingDirectory)/BuildLogs' + CleanTargetFolder: true + continueOnError: true + condition: succeededOrFailed() + +- task: PublishPipelineArtifact@1 + displayName: Publish BuildLogs + inputs: + targetPath: '$(Build.StagingDirectory)/BuildLogs' + artifactName: BuildLogs_SourceBuild_${{ parameters.platform.name }}_Attempt$(System.JobAttempt) + continueOnError: true + condition: succeededOrFailed() + +# Manually inject component detection so that we can ignore the source build upstream cache, which contains +# a nupkg cache of input packages (a local feed). +# This path must match the upstream cache path in property 'CurrentRepoSourceBuiltNupkgCacheDir' +# in src\Microsoft.DotNet.Arcade.Sdk\tools\SourceBuild\SourceBuildArcade.targets +- task: ComponentGovernanceComponentDetection@0 + displayName: Component Detection (Exclude upstream cache) + inputs: + ignoreDirectories: '$(Build.SourcesDirectory)/artifacts/source-build/self/src/artifacts/obj/source-built-upstream-cache' diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/telemetry-end.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/telemetry-end.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/telemetry-end.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/telemetry-end.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,102 @@ +parameters: + maxRetries: 5 + retryDelay: 10 # in seconds + +steps: +- bash: | + if [ "$AGENT_JOBSTATUS" = "Succeeded" ] || [ "$AGENT_JOBSTATUS" = "PartiallySucceeded" ]; then + errorCount=0 + else + errorCount=1 + fi + warningCount=0 + + curlStatus=1 + retryCount=0 + # retry loop to harden against spotty telemetry connections + # we don't retry successes and 4xx client errors + until [[ $curlStatus -eq 0 || ( $curlStatus -ge 400 && $curlStatus -le 499 ) || $retryCount -ge $MaxRetries ]] + do + if [ $retryCount -gt 0 ]; then + echo "Failed to send telemetry to Helix; waiting $RetryDelay seconds before retrying..." + sleep $RetryDelay + fi + + # create a temporary file for curl output + res=`mktemp` + + curlResult=` + curl --verbose --output $res --write-out "%{http_code}"\ + -H 'Content-Type: application/json' \ + -H "X-Helix-Job-Token: $Helix_JobToken" \ + -H 'Content-Length: 0' \ + -X POST -G "https://helix.dot.net/api/2018-03-14/telemetry/job/build/$Helix_WorkItemId/finish" \ + --data-urlencode "errorCount=$errorCount" \ + --data-urlencode "warningCount=$warningCount"` + curlStatus=$? + + if [ $curlStatus -eq 0 ]; then + if [ $curlResult -gt 299 ] || [ $curlResult -lt 200 ]; then + curlStatus=$curlResult + fi + fi + + let retryCount++ + done + + if [ $curlStatus -ne 0 ]; then + echo "Failed to Send Build Finish information after $retryCount retries" + vstsLogOutput="vso[task.logissue type=error;sourcepath=templates/steps/telemetry-end.yml;code=1;]Failed to Send Build Finish information: $curlStatus" + echo "##$vstsLogOutput" + exit 1 + fi + displayName: Send Unix Build End Telemetry + env: + # defined via VSTS variables in start-job.sh + Helix_JobToken: $(Helix_JobToken) + Helix_WorkItemId: $(Helix_WorkItemId) + MaxRetries: ${{ parameters.maxRetries }} + RetryDelay: ${{ parameters.retryDelay }} + condition: and(always(), ne(variables['Agent.Os'], 'Windows_NT')) +- powershell: | + if (($env:Agent_JobStatus -eq 'Succeeded') -or ($env:Agent_JobStatus -eq 'PartiallySucceeded')) { + $ErrorCount = 0 + } else { + $ErrorCount = 1 + } + $WarningCount = 0 + + # Basic retry loop to harden against server flakiness + $retryCount = 0 + while ($retryCount -lt $env:MaxRetries) { + try { + Invoke-RestMethod -Uri "https://helix.dot.net/api/2018-03-14/telemetry/job/build/$env:Helix_WorkItemId/finish?errorCount=$ErrorCount&warningCount=$WarningCount" -Method Post -ContentType "application/json" -Body "" ` + -Headers @{ 'X-Helix-Job-Token'=$env:Helix_JobToken } + break + } + catch { + $statusCode = $_.Exception.Response.StatusCode.value__ + if ($statusCode -ge 400 -and $statusCode -le 499) { + Write-Host "##vso[task.logissue]error Failed to send telemetry to Helix (status code $statusCode); not retrying (4xx client error)" + Write-Host "##vso[task.logissue]error ", $_.Exception.GetType().FullName, $_.Exception.Message + exit 1 + } + Write-Host "Failed to send telemetry to Helix (status code $statusCode); waiting $env:RetryDelay seconds before retrying..." + $retryCount++ + sleep $env:RetryDelay + continue + } + } + + if ($retryCount -ge $env:MaxRetries) { + Write-Host "##vso[task.logissue]error Failed to send telemetry to Helix after $retryCount retries." + exit 1 + } + displayName: Send Windows Build End Telemetry + env: + # defined via VSTS variables in start-job.ps1 + Helix_JobToken: $(Helix_JobToken) + Helix_WorkItemId: $(Helix_WorkItemId) + MaxRetries: ${{ parameters.maxRetries }} + RetryDelay: ${{ parameters.retryDelay }} + condition: and(always(),eq(variables['Agent.Os'], 'Windows_NT')) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/telemetry-start.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/telemetry-start.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/templates/steps/telemetry-start.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/templates/steps/telemetry-start.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,241 @@ +parameters: + helixSource: 'undefined_defaulted_in_telemetry.yml' + helixType: 'undefined_defaulted_in_telemetry.yml' + buildConfig: '' + runAsPublic: false + maxRetries: 5 + retryDelay: 10 # in seconds + +steps: +- ${{ if and(eq(parameters.runAsPublic, 'false'), not(eq(variables['System.TeamProject'], 'public'))) }}: + - task: AzureKeyVault@1 + inputs: + azureSubscription: 'HelixProd_KeyVault' + KeyVaultName: HelixProdKV + SecretsFilter: 'HelixApiAccessToken' + condition: always() +- bash: | + # create a temporary file + jobInfo=`mktemp` + + # write job info content to temporary file + cat > $jobInfo < powershell invocations +# as dot sourcing isn't possible. +function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { + if (Test-Path variable:global:_DotNetInstallDir) { + return $global:_DotNetInstallDir + } + + # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism + $env:DOTNET_MULTILEVEL_LOOKUP=0 + + # Disable first run since we do not need all ASP.NET packages restored. + $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + + # Disable telemetry on CI. + if ($ci) { + $env:DOTNET_CLI_TELEMETRY_OPTOUT=1 + } + + # Source Build uses DotNetCoreSdkDir variable + if ($env:DotNetCoreSdkDir -ne $null) { + $env:DOTNET_INSTALL_DIR = $env:DotNetCoreSdkDir + } + + # Find the first path on %PATH% that contains the dotnet.exe + if ($useInstalledDotNetCli -and (-not $globalJsonHasRuntimes) -and ($env:DOTNET_INSTALL_DIR -eq $null)) { + $dotnetExecutable = GetExecutableFileName 'dotnet' + $dotnetCmd = Get-Command $dotnetExecutable -ErrorAction SilentlyContinue + + if ($dotnetCmd -ne $null) { + $env:DOTNET_INSTALL_DIR = Split-Path $dotnetCmd.Path -Parent + } + } + + $dotnetSdkVersion = $GlobalJson.tools.dotnet + + # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, + # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues. + if ((-not $globalJsonHasRuntimes) -and (-not [string]::IsNullOrEmpty($env:DOTNET_INSTALL_DIR)) -and (Test-Path(Join-Path $env:DOTNET_INSTALL_DIR "sdk\$dotnetSdkVersion"))) { + $dotnetRoot = $env:DOTNET_INSTALL_DIR + } else { + $dotnetRoot = Join-Path $RepoRoot '.dotnet' + + if (-not (Test-Path(Join-Path $dotnetRoot "sdk\$dotnetSdkVersion"))) { + if ($install) { + InstallDotNetSdk $dotnetRoot $dotnetSdkVersion + } else { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to find dotnet with SDK version '$dotnetSdkVersion'" + ExitWithExitCode 1 + } + } + + $env:DOTNET_INSTALL_DIR = $dotnetRoot + } + + # Creates a temporary file under the toolset dir. + # The following code block is protecting against concurrent access so that this function can + # be called in parallel. + if ($createSdkLocationFile) { + do { + $sdkCacheFileTemp = Join-Path $ToolsetDir $([System.IO.Path]::GetRandomFileName()) + } + until (!(Test-Path $sdkCacheFileTemp)) + Set-Content -Path $sdkCacheFileTemp -Value $dotnetRoot + + try { + Move-Item -Force $sdkCacheFileTemp (Join-Path $ToolsetDir 'sdk.txt') + } catch { + # Somebody beat us + Remove-Item -Path $sdkCacheFileTemp + } + } + + # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom + # build steps from using anything other than what we've downloaded. + # It also ensures that VS msbuild will use the downloaded sdk targets. + $env:PATH = "$dotnetRoot;$env:PATH" + + # Make Sure that our bootstrapped dotnet cli is available in future steps of the Azure Pipelines build + Write-PipelinePrependPath -Path $dotnetRoot + + Write-PipelineSetVariable -Name 'DOTNET_MULTILEVEL_LOOKUP' -Value '0' + Write-PipelineSetVariable -Name 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' -Value '1' + + return $global:_DotNetInstallDir = $dotnetRoot +} + +function Retry($downloadBlock, $maxRetries = 5) { + $retries = 1 + + while($true) { + try { + & $downloadBlock + break + } + catch { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ + } + + if (++$retries -le $maxRetries) { + $delayInSeconds = [math]::Pow(2, $retries) - 1 # Exponential backoff + Write-Host "Retrying. Waiting for $delayInSeconds seconds before next attempt ($retries of $maxRetries)." + Start-Sleep -Seconds $delayInSeconds + } + else { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to download file in $maxRetries attempts." + break + } + + } +} + +function GetDotNetInstallScript([string] $dotnetRoot) { + $installScript = Join-Path $dotnetRoot 'dotnet-install.ps1' + if (!(Test-Path $installScript)) { + Create-Directory $dotnetRoot + $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit + $uri = "https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1" + + Retry({ + Write-Host "GET $uri" + Invoke-WebRequest $uri -OutFile $installScript + }) + } + + return $installScript +} + +function InstallDotNetSdk([string] $dotnetRoot, [string] $version, [string] $architecture = '', [switch] $noPath) { + InstallDotNet $dotnetRoot $version $architecture '' $false $runtimeSourceFeed $runtimeSourceFeedKey -noPath:$noPath +} + +function InstallDotNet([string] $dotnetRoot, + [string] $version, + [string] $architecture = '', + [string] $runtime = '', + [bool] $skipNonVersionedFiles = $false, + [string] $runtimeSourceFeed = '', + [string] $runtimeSourceFeedKey = '', + [switch] $noPath) { + + $dotnetVersionLabel = "'sdk v$version'" + + if ($runtime -ne '' -and $runtime -ne 'sdk') { + $runtimePath = $dotnetRoot + $runtimePath = $runtimePath + "\shared" + if ($runtime -eq "dotnet") { $runtimePath = $runtimePath + "\Microsoft.NETCore.App" } + if ($runtime -eq "aspnetcore") { $runtimePath = $runtimePath + "\Microsoft.AspNetCore.App" } + if ($runtime -eq "windowsdesktop") { $runtimePath = $runtimePath + "\Microsoft.WindowsDesktop.App" } + $runtimePath = $runtimePath + "\" + $version + + $dotnetVersionLabel = "runtime toolset '$runtime/$architecture v$version'" + + if (Test-Path $runtimePath) { + Write-Host " Runtime toolset '$runtime/$architecture v$version' already installed." + $installSuccess = $true + Exit + } + } + + $installScript = GetDotNetInstallScript $dotnetRoot + $installParameters = @{ + Version = $version + InstallDir = $dotnetRoot + } + + if ($architecture) { $installParameters.Architecture = $architecture } + if ($runtime) { $installParameters.Runtime = $runtime } + if ($skipNonVersionedFiles) { $installParameters.SkipNonVersionedFiles = $skipNonVersionedFiles } + if ($noPath) { $installParameters.NoPath = $True } + + $variations = @() + $variations += @($installParameters) + + $dotnetBuilds = $installParameters.Clone() + $dotnetbuilds.AzureFeed = "https://dotnetbuilds.azureedge.net/public" + $variations += @($dotnetBuilds) + + if ($runtimeSourceFeed) { + $runtimeSource = $installParameters.Clone() + $runtimeSource.AzureFeed = $runtimeSourceFeed + if ($runtimeSourceFeedKey) { + $decodedBytes = [System.Convert]::FromBase64String($runtimeSourceFeedKey) + $decodedString = [System.Text.Encoding]::UTF8.GetString($decodedBytes) + $runtimeSource.FeedCredential = $decodedString + } + $variations += @($runtimeSource) + } + + $installSuccess = $false + foreach ($variation in $variations) { + if ($variation | Get-Member AzureFeed) { + $location = $variation.AzureFeed + } else { + $location = "public location"; + } + Write-Host " Attempting to install $dotnetVersionLabel from $location." + try { + & $installScript @variation + $installSuccess = $true + break + } + catch { + Write-Host " Failed to install $dotnetVersionLabel from $location." + } + } + if (-not $installSuccess) { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install $dotnetVersionLabel from any of the specified locations." + ExitWithExitCode 1 + } +} + +# +# Locates Visual Studio MSBuild installation. +# The preference order for MSBuild to use is as follows: +# +# 1. MSBuild from an active VS command prompt +# 2. MSBuild from a compatible VS installation +# 3. MSBuild from the xcopy tool package +# +# Returns full path to msbuild.exe. +# Throws on failure. +# +function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = $null) { + if (-not (IsWindowsPlatform)) { + throw "Cannot initialize Visual Studio on non-Windows" + } + + if (Test-Path variable:global:_MSBuildExe) { + return $global:_MSBuildExe + } + + # Minimum VS version to require. + $vsMinVersionReqdStr = '17.7' + $vsMinVersionReqd = [Version]::new($vsMinVersionReqdStr) + + # If the version of msbuild is going to be xcopied, + # use this version. Version matches a package here: + # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/RoslynTools.MSBuild/versions/17.8.1-2 + $defaultXCopyMSBuildVersion = '17.8.1-2' + + if (!$vsRequirements) { + if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { + $vsRequirements = $GlobalJson.tools.vs + } + else { + $vsRequirements = New-Object PSObject -Property @{ version = $vsMinVersionReqdStr } + } + } + $vsMinVersionStr = if ($vsRequirements.version) { $vsRequirements.version } else { $vsMinVersionReqdStr } + $vsMinVersion = [Version]::new($vsMinVersionStr) + + # Try msbuild command available in the environment. + if ($env:VSINSTALLDIR -ne $null) { + $msbuildCmd = Get-Command 'msbuild.exe' -ErrorAction SilentlyContinue + if ($msbuildCmd -ne $null) { + # Workaround for https://github.com/dotnet/roslyn/issues/35793 + # Due to this issue $msbuildCmd.Version returns 0.0.0.0 for msbuild.exe 16.2+ + $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split([char[]]@('-', '+'))[0]) + + if ($msbuildVersion -ge $vsMinVersion) { + return $global:_MSBuildExe = $msbuildCmd.Path + } + + # Report error - the developer environment is initialized with incompatible VS version. + throw "Developer Command Prompt for VS $($env:VisualStudioVersion) is not recent enough. Please upgrade to $vsMinVersionStr or build from a plain CMD window" + } + } + + # Locate Visual Studio installation or download x-copy msbuild. + $vsInfo = LocateVisualStudio $vsRequirements + if ($vsInfo -ne $null) { + # Ensure vsInstallDir has a trailing slash + $vsInstallDir = Join-Path $vsInfo.installationPath "\" + $vsMajorVersion = $vsInfo.installationVersion.Split('.')[0] + + InitializeVisualStudioEnvironmentVariables $vsInstallDir $vsMajorVersion + } else { + + if (Get-Member -InputObject $GlobalJson.tools -Name 'xcopy-msbuild') { + $xcopyMSBuildVersion = $GlobalJson.tools.'xcopy-msbuild' + $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0] + } else { + #if vs version provided in global.json is incompatible (too low) then use the default version for xcopy msbuild download + if($vsMinVersion -lt $vsMinVersionReqd){ + Write-Host "Using xcopy-msbuild version of $defaultXCopyMSBuildVersion since VS version $vsMinVersionStr provided in global.json is not compatible" + $xcopyMSBuildVersion = $defaultXCopyMSBuildVersion + $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0] + } + else{ + # If the VS version IS compatible, look for an xcopy msbuild package + # with a version matching VS. + # Note: If this version does not exist, then an explicit version of xcopy msbuild + # can be specified in global.json. This will be required for pre-release versions of msbuild. + $vsMajorVersion = $vsMinVersion.Major + $vsMinorVersion = $vsMinVersion.Minor + $xcopyMSBuildVersion = "$vsMajorVersion.$vsMinorVersion.0" + } + } + + $vsInstallDir = $null + if ($xcopyMSBuildVersion.Trim() -ine "none") { + $vsInstallDir = InitializeXCopyMSBuild $xcopyMSBuildVersion $install + if ($vsInstallDir -eq $null) { + throw "Could not xcopy msbuild. Please check that package 'RoslynTools.MSBuild @ $xcopyMSBuildVersion' exists on feed 'dotnet-eng'." + } + } + if ($vsInstallDir -eq $null) { + throw 'Unable to find Visual Studio that has required version and components installed' + } + } + + $msbuildVersionDir = if ([int]$vsMajorVersion -lt 16) { "$vsMajorVersion.0" } else { "Current" } + + $local:BinFolder = Join-Path $vsInstallDir "MSBuild\$msbuildVersionDir\Bin" + $local:Prefer64bit = if (Get-Member -InputObject $vsRequirements -Name 'Prefer64bit') { $vsRequirements.Prefer64bit } else { $false } + if ($local:Prefer64bit -and (Test-Path(Join-Path $local:BinFolder "amd64"))) { + $global:_MSBuildExe = Join-Path $local:BinFolder "amd64\msbuild.exe" + } else { + $global:_MSBuildExe = Join-Path $local:BinFolder "msbuild.exe" + } + + return $global:_MSBuildExe +} + +function InitializeVisualStudioEnvironmentVariables([string] $vsInstallDir, [string] $vsMajorVersion) { + $env:VSINSTALLDIR = $vsInstallDir + Set-Item "env:VS$($vsMajorVersion)0COMNTOOLS" (Join-Path $vsInstallDir "Common7\Tools\") + + $vsSdkInstallDir = Join-Path $vsInstallDir "VSSDK\" + if (Test-Path $vsSdkInstallDir) { + Set-Item "env:VSSDK$($vsMajorVersion)0Install" $vsSdkInstallDir + $env:VSSDKInstall = $vsSdkInstallDir + } +} + +function InstallXCopyMSBuild([string]$packageVersion) { + return InitializeXCopyMSBuild $packageVersion -install $true +} + +function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { + $packageName = 'RoslynTools.MSBuild' + $packageDir = Join-Path $ToolsDir "msbuild\$packageVersion" + $packagePath = Join-Path $packageDir "$packageName.$packageVersion.nupkg" + + if (!(Test-Path $packageDir)) { + if (!$install) { + return $null + } + + Create-Directory $packageDir + + Write-Host "Downloading $packageName $packageVersion" + $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit + Retry({ + Invoke-WebRequest "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/flat2/$packageName/$packageVersion/$packageName.$packageVersion.nupkg" -OutFile $packagePath + }) + + Unzip $packagePath $packageDir + } + + return Join-Path $packageDir 'tools' +} + +# +# Locates Visual Studio instance that meets the minimal requirements specified by tools.vs object in global.json. +# +# The following properties of tools.vs are recognized: +# "version": "{major}.{minor}" +# Two part minimal VS version, e.g. "15.9", "16.0", etc. +# "components": ["componentId1", "componentId2", ...] +# Array of ids of workload components that must be available in the VS instance. +# See e.g. https://docs.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-enterprise?view=vs-2017 +# +# Returns JSON describing the located VS instance (same format as returned by vswhere), +# or $null if no instance meeting the requirements is found on the machine. +# +function LocateVisualStudio([object]$vsRequirements = $null){ + if (-not (IsWindowsPlatform)) { + throw "Cannot run vswhere on non-Windows platforms." + } + + if (Get-Member -InputObject $GlobalJson.tools -Name 'vswhere') { + $vswhereVersion = $GlobalJson.tools.vswhere + } else { + $vswhereVersion = '2.5.2' + } + + $vsWhereDir = Join-Path $ToolsDir "vswhere\$vswhereVersion" + $vsWhereExe = Join-Path $vsWhereDir 'vswhere.exe' + + if (!(Test-Path $vsWhereExe)) { + Create-Directory $vsWhereDir + Write-Host 'Downloading vswhere' + Retry({ + Invoke-WebRequest "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/windows/vswhere/$vswhereVersion/vswhere.exe" -OutFile $vswhereExe + }) + } + + if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs } + $args = @('-latest', '-format', 'json', '-requires', 'Microsoft.Component.MSBuild', '-products', '*') + + if (!$excludePrereleaseVS) { + $args += '-prerelease' + } + + if (Get-Member -InputObject $vsRequirements -Name 'version') { + $args += '-version' + $args += $vsRequirements.version + } + + if (Get-Member -InputObject $vsRequirements -Name 'components') { + foreach ($component in $vsRequirements.components) { + $args += '-requires' + $args += $component + } + } + + $vsInfo =& $vsWhereExe $args | ConvertFrom-Json + + if ($lastExitCode -ne 0) { + return $null + } + + # use first matching instance + return $vsInfo[0] +} + +function InitializeBuildTool() { + if (Test-Path variable:global:_BuildTool) { + # If the requested msbuild parameters do not match, clear the cached variables. + if($global:_BuildTool.Contains('ExcludePrereleaseVS') -and $global:_BuildTool.ExcludePrereleaseVS -ne $excludePrereleaseVS) { + Remove-Item variable:global:_BuildTool + Remove-Item variable:global:_MSBuildExe + } else { + return $global:_BuildTool + } + } + + if (-not $msbuildEngine) { + $msbuildEngine = GetDefaultMSBuildEngine + } + + # Initialize dotnet cli if listed in 'tools' + $dotnetRoot = $null + if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { + $dotnetRoot = InitializeDotNetCli -install:$restore + } + + if ($msbuildEngine -eq 'dotnet') { + if (!$dotnetRoot) { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "/global.json must specify 'tools.dotnet'." + ExitWithExitCode 1 + } + $dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet') + $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'net8.0' } + } elseif ($msbuildEngine -eq "vs") { + try { + $msbuildPath = InitializeVisualStudioMSBuild -install:$restore + } catch { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ + ExitWithExitCode 1 + } + + $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "net472"; ExcludePrereleaseVS = $excludePrereleaseVS } + } else { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." + ExitWithExitCode 1 + } + + return $global:_BuildTool = $buildTool +} + +function GetDefaultMSBuildEngine() { + # Presence of tools.vs indicates the repo needs to build using VS msbuild on Windows. + if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { + return 'vs' + } + + if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { + return 'dotnet' + } + + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'." + ExitWithExitCode 1 +} + +function GetNuGetPackageCachePath() { + if ($env:NUGET_PACKAGES -eq $null) { + # Use local cache on CI to ensure deterministic build. + # Avoid using the http cache as workaround for https://github.com/NuGet/Home/issues/3116 + # use global cache in dev builds to avoid cost of downloading packages. + # For directory normalization, see also: https://github.com/NuGet/Home/issues/7968 + if ($useGlobalNuGetCache) { + $env:NUGET_PACKAGES = Join-Path $env:UserProfile '.nuget\packages\' + } else { + $env:NUGET_PACKAGES = Join-Path $RepoRoot '.packages\' + $env:RESTORENOCACHE = $true + } + } + + return $env:NUGET_PACKAGES +} + +# Returns a full path to an Arcade SDK task project file. +function GetSdkTaskProject([string]$taskName) { + return Join-Path (Split-Path (InitializeToolset) -Parent) "SdkTasks\$taskName.proj" +} + +function InitializeNativeTools() { + if (-Not (Test-Path variable:DisableNativeToolsetInstalls) -And (Get-Member -InputObject $GlobalJson -Name "native-tools")) { + $nativeArgs= @{} + if ($ci) { + $nativeArgs = @{ + InstallDirectory = "$ToolsDir" + } + } + if ($env:NativeToolsOnMachine) { + Write-Host "Variable NativeToolsOnMachine detected, enabling native tool path promotion..." + $nativeArgs += @{ PathPromotion = $true } + } + & "$PSScriptRoot/init-tools-native.ps1" @nativeArgs + } +} + +function Read-ArcadeSdkVersion() { + return $GlobalJson.'msbuild-sdks'.'Microsoft.DotNet.Arcade.Sdk' +} + +function InitializeToolset() { + if (Test-Path variable:global:_ToolsetBuildProj) { + return $global:_ToolsetBuildProj + } + + $nugetCache = GetNuGetPackageCachePath + + $toolsetVersion = Read-ArcadeSdkVersion + $toolsetLocationFile = Join-Path $ToolsetDir "$toolsetVersion.txt" + + if (Test-Path $toolsetLocationFile) { + $path = Get-Content $toolsetLocationFile -TotalCount 1 + if (Test-Path $path) { + return $global:_ToolsetBuildProj = $path + } + } + + if (-not $restore) { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Toolset version $toolsetVersion has not been restored." + ExitWithExitCode 1 + } + + $buildTool = InitializeBuildTool + + $proj = Join-Path $ToolsetDir 'restore.proj' + $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'ToolsetRestore.binlog') } else { '' } + + '' | Set-Content $proj + + MSBuild-Core $proj $bl /t:__WriteToolsetLocation /clp:ErrorsOnly`;NoSummary /p:__ToolsetLocationOutputFile=$toolsetLocationFile + + $path = Get-Content $toolsetLocationFile -Encoding UTF8 -TotalCount 1 + if (!(Test-Path $path)) { + throw "Invalid toolset path: $path" + } + + return $global:_ToolsetBuildProj = $path +} + +function ExitWithExitCode([int] $exitCode) { + if ($ci -and $prepareMachine) { + Stop-Processes + } + exit $exitCode +} + +# Check if $LASTEXITCODE is a nonzero exit code (NZEC). If so, print a Azure Pipeline error for +# diagnostics, then exit the script with the $LASTEXITCODE. +function Exit-IfNZEC([string] $category = "General") { + Write-Host "Exit code $LASTEXITCODE" + if ($LASTEXITCODE -ne 0) { + $message = "Last command failed with exit code $LASTEXITCODE." + Write-PipelineTelemetryError -Force -Category $category -Message $message + ExitWithExitCode $LASTEXITCODE + } +} + +function Stop-Processes() { + Write-Host 'Killing running build processes...' + foreach ($processName in $processesToStopOnExit) { + Get-Process -Name $processName -ErrorAction SilentlyContinue | Stop-Process + } +} + +# +# Executes msbuild (or 'dotnet msbuild') with arguments passed to the function. +# The arguments are automatically quoted. +# Terminates the script if the build fails. +# +function MSBuild() { + if ($pipelinesLog) { + $buildTool = InitializeBuildTool + + if ($ci -and $buildTool.Tool -eq 'dotnet') { + $env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS = 20 + $env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS = 20 + Write-PipelineSetVariable -Name 'NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS' -Value '20' + Write-PipelineSetVariable -Name 'NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS' -Value '20' + } + + Enable-Nuget-EnhancedRetry + + $toolsetBuildProject = InitializeToolset + $basePath = Split-Path -parent $toolsetBuildProject + $possiblePaths = @( + # new scripts need to work with old packages, so we need to look for the old names/versions + (Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.ArcadeLogging.dll')), + (Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.Arcade.Sdk.dll')), + (Join-Path $basePath (Join-Path netcoreapp2.1 'Microsoft.DotNet.ArcadeLogging.dll')), + (Join-Path $basePath (Join-Path netcoreapp2.1 'Microsoft.DotNet.Arcade.Sdk.dll')) + (Join-Path $basePath (Join-Path netcoreapp3.1 'Microsoft.DotNet.ArcadeLogging.dll')), + (Join-Path $basePath (Join-Path netcoreapp3.1 'Microsoft.DotNet.Arcade.Sdk.dll')) + (Join-Path $basePath (Join-Path net7.0 'Microsoft.DotNet.ArcadeLogging.dll')), + (Join-Path $basePath (Join-Path net7.0 'Microsoft.DotNet.Arcade.Sdk.dll')) + ) + $selectedPath = $null + foreach ($path in $possiblePaths) { + if (Test-Path $path -PathType Leaf) { + $selectedPath = $path + break + } + } + if (-not $selectedPath) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Unable to find arcade sdk logger assembly.' + ExitWithExitCode 1 + } + $args += "/logger:$selectedPath" + } + + MSBuild-Core @args +} + +# +# Executes msbuild (or 'dotnet msbuild') with arguments passed to the function. +# The arguments are automatically quoted. +# Terminates the script if the build fails. +# +function MSBuild-Core() { + if ($ci) { + if (!$binaryLog -and !$excludeCIBinarylog) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Binary log must be enabled in CI build, or explicitly opted-out from with the -excludeCIBinarylog switch.' + ExitWithExitCode 1 + } + + if ($nodeReuse) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Node reuse must be disabled in CI build.' + ExitWithExitCode 1 + } + } + + Enable-Nuget-EnhancedRetry + + $buildTool = InitializeBuildTool + + $cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci" + + if ($warnAsError) { + $cmdArgs += ' /warnaserror /p:TreatWarningsAsErrors=true' + } + else { + $cmdArgs += ' /p:TreatWarningsAsErrors=false' + } + + foreach ($arg in $args) { + if ($null -ne $arg -and $arg.Trim() -ne "") { + if ($arg.EndsWith('\')) { + $arg = $arg + "\" + } + $cmdArgs += " `"$arg`"" + } + } + + $env:ARCADE_BUILD_TOOL_COMMAND = "$($buildTool.Path) $cmdArgs" + + $exitCode = Exec-Process $buildTool.Path $cmdArgs + + if ($exitCode -ne 0) { + # We should not Write-PipelineTaskError here because that message shows up in the build summary + # The build already logged an error, that's the reason it failed. Producing an error here only adds noise. + Write-Host "Build failed with exit code $exitCode. Check errors above." -ForegroundColor Red + + $buildLog = GetMSBuildBinaryLogCommandLineArgument $args + if ($null -ne $buildLog) { + Write-Host "See log: $buildLog" -ForegroundColor DarkGray + } + + # When running on Azure Pipelines, override the returned exit code to avoid double logging. + if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null) { + Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed." + # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error + # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error + ExitWithExitCode 0 + } else { + ExitWithExitCode $exitCode + } + } +} + +function GetMSBuildBinaryLogCommandLineArgument($arguments) { + foreach ($argument in $arguments) { + if ($argument -ne $null) { + $arg = $argument.Trim() + if ($arg.StartsWith('/bl:', "OrdinalIgnoreCase")) { + return $arg.Substring('/bl:'.Length) + } + + if ($arg.StartsWith('/binaryLogger:', 'OrdinalIgnoreCase')) { + return $arg.Substring('/binaryLogger:'.Length) + } + } + } + + return $null +} + +function GetExecutableFileName($baseName) { + if (IsWindowsPlatform) { + return "$baseName.exe" + } + else { + return $baseName + } +} + +function IsWindowsPlatform() { + return [environment]::OSVersion.Platform -eq [PlatformID]::Win32NT +} + +function Get-Darc($version) { + $darcPath = "$TempDir\darc\$(New-Guid)" + if ($version -ne $null) { + & $PSScriptRoot\darc-init.ps1 -toolpath $darcPath -darcVersion $version | Out-Host + } else { + & $PSScriptRoot\darc-init.ps1 -toolpath $darcPath | Out-Host + } + return "$darcPath\darc.exe" +} + +. $PSScriptRoot\pipeline-logging-functions.ps1 + +$RepoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..\') +$EngRoot = Resolve-Path (Join-Path $PSScriptRoot '..') +$ArtifactsDir = Join-Path $RepoRoot 'artifacts' +$ToolsetDir = Join-Path $ArtifactsDir 'toolset' +$ToolsDir = Join-Path $RepoRoot '.tools' +$LogDir = Join-Path (Join-Path $ArtifactsDir 'log') $configuration +$TempDir = Join-Path (Join-Path $ArtifactsDir 'tmp') $configuration +$GlobalJson = Get-Content -Raw -Path (Join-Path $RepoRoot 'global.json') | ConvertFrom-Json +# true if global.json contains a "runtimes" section +$globalJsonHasRuntimes = if ($GlobalJson.tools.PSObject.Properties.Name -Match 'runtimes') { $true } else { $false } + +Create-Directory $ToolsetDir +Create-Directory $TempDir +Create-Directory $LogDir + +Write-PipelineSetVariable -Name 'Artifacts' -Value $ArtifactsDir +Write-PipelineSetVariable -Name 'Artifacts.Toolset' -Value $ToolsetDir +Write-PipelineSetVariable -Name 'Artifacts.Log' -Value $LogDir +Write-PipelineSetVariable -Name 'TEMP' -Value $TempDir +Write-PipelineSetVariable -Name 'TMP' -Value $TempDir + +# Import custom tools configuration, if present in the repo. +# Note: Import in global scope so that the script set top-level variables without qualification. +if (!$disableConfigureToolsetImport) { + $configureToolsetScript = Join-Path $EngRoot 'configure-toolset.ps1' + if (Test-Path $configureToolsetScript) { + . $configureToolsetScript + if ((Test-Path variable:failOnConfigureToolsetError) -And $failOnConfigureToolsetError) { + if ((Test-Path variable:LastExitCode) -And ($LastExitCode -ne 0)) { + Write-PipelineTelemetryError -Category 'Build' -Message 'configure-toolset.ps1 returned a non-zero exit code' + ExitWithExitCode $LastExitCode + } + } + } +} + +# +# If $ci flag is set, turn on (and log that we did) special environment variables for improved Nuget client retry logic. +# +function Enable-Nuget-EnhancedRetry() { + if ($ci) { + Write-Host "Setting NUGET enhanced retry environment variables" + $env:NUGET_ENABLE_ENHANCED_HTTP_RETRY = 'true' + $env:NUGET_ENHANCED_MAX_NETWORK_TRY_COUNT = 6 + $env:NUGET_ENHANCED_NETWORK_RETRY_DELAY_MILLISECONDS = 1000 + $env:NUGET_RETRY_HTTP_429 = 'true' + Write-PipelineSetVariable -Name 'NUGET_ENABLE_ENHANCED_HTTP_RETRY' -Value 'true' + Write-PipelineSetVariable -Name 'NUGET_ENHANCED_MAX_NETWORK_TRY_COUNT' -Value '6' + Write-PipelineSetVariable -Name 'NUGET_ENHANCED_NETWORK_RETRY_DELAY_MILLISECONDS' -Value '1000' + Write-PipelineSetVariable -Name 'NUGET_RETRY_HTTP_429' -Value 'true' + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/tools.sh dotnet8-8.0.100-8.0.0/src/aspire/eng/common/tools.sh --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/common/tools.sh 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/common/tools.sh 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,587 @@ +#!/usr/bin/env bash + +# Initialize variables if they aren't already defined. + +# CI mode - set to true on CI server for PR validation build or official build. +ci=${ci:-false} + +# Set to true to use the pipelines logger which will enable Azure logging output. +# https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md +# This flag is meant as a temporary opt-opt for the feature while validate it across +# our consumers. It will be deleted in the future. +if [[ "$ci" == true ]]; then + pipelines_log=${pipelines_log:-true} +else + pipelines_log=${pipelines_log:-false} +fi + +# Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. +configuration=${configuration:-'Debug'} + +# Set to true to opt out of outputting binary log while running in CI +exclude_ci_binary_log=${exclude_ci_binary_log:-false} + +if [[ "$ci" == true && "$exclude_ci_binary_log" == false ]]; then + binary_log_default=true +else + binary_log_default=false +fi + +# Set to true to output binary log from msbuild. Note that emitting binary log slows down the build. +binary_log=${binary_log:-$binary_log_default} + +# Turns on machine preparation/clean up code that changes the machine state (e.g. kills build processes). +prepare_machine=${prepare_machine:-false} + +# True to restore toolsets and dependencies. +restore=${restore:-true} + +# Adjusts msbuild verbosity level. +verbosity=${verbosity:-'minimal'} + +# Set to true to reuse msbuild nodes. Recommended to not reuse on CI. +if [[ "$ci" == true ]]; then + node_reuse=${node_reuse:-false} +else + node_reuse=${node_reuse:-true} +fi + +# Configures warning treatment in msbuild. +warn_as_error=${warn_as_error:-true} + +# True to attempt using .NET Core already that meets requirements specified in global.json +# installed on the machine instead of downloading one. +use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} + +# Enable repos to use a particular version of the on-line dotnet-install scripts. +# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh +dotnetInstallScriptVersion=${dotnetInstallScriptVersion:-'v1'} + +# True to use global NuGet cache instead of restoring packages to repository-local directory. +if [[ "$ci" == true ]]; then + use_global_nuget_cache=${use_global_nuget_cache:-false} +else + use_global_nuget_cache=${use_global_nuget_cache:-true} +fi + +# Used when restoring .NET SDK from alternative feeds +runtime_source_feed=${runtime_source_feed:-''} +runtime_source_feed_key=${runtime_source_feed_key:-''} + +# Resolve any symlinks in the given path. +function ResolvePath { + local path=$1 + + while [[ -h $path ]]; do + local dir="$( cd -P "$( dirname "$path" )" && pwd )" + path="$(readlink "$path")" + + # if $path was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $path != /* ]] && path="$dir/$path" + done + + # return value + _ResolvePath="$path" +} + +# ReadVersionFromJson [json key] +function ReadGlobalVersion { + local key=$1 + + if command -v jq &> /dev/null; then + _ReadGlobalVersion="$(jq -r ".[] | select(has(\"$key\")) | .\"$key\"" "$global_json_file")" + elif [[ "$(cat "$global_json_file")" =~ \"$key\"[[:space:]\:]*\"([^\"]+) ]]; then + _ReadGlobalVersion=${BASH_REMATCH[1]} + fi + + if [[ -z "$_ReadGlobalVersion" ]]; then + Write-PipelineTelemetryError -category 'Build' "Error: Cannot find \"$key\" in $global_json_file" + ExitWithExitCode 1 + fi +} + +function InitializeDotNetCli { + if [[ -n "${_InitializeDotNetCli:-}" ]]; then + return + fi + + local install=$1 + + # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism + export DOTNET_MULTILEVEL_LOOKUP=0 + + # Disable first run since we want to control all package sources + export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + + # Disable telemetry on CI + if [[ $ci == true ]]; then + export DOTNET_CLI_TELEMETRY_OPTOUT=1 + fi + + # LTTNG is the logging infrastructure used by Core CLR. Need this variable set + # so it doesn't output warnings to the console. + export LTTNG_HOME="$HOME" + + # Source Build uses DotNetCoreSdkDir variable + if [[ -n "${DotNetCoreSdkDir:-}" ]]; then + export DOTNET_INSTALL_DIR="$DotNetCoreSdkDir" + fi + + # Find the first path on $PATH that contains the dotnet.exe + if [[ "$use_installed_dotnet_cli" == true && $global_json_has_runtimes == false && -z "${DOTNET_INSTALL_DIR:-}" ]]; then + local dotnet_path=`command -v dotnet` + if [[ -n "$dotnet_path" ]]; then + ResolvePath "$dotnet_path" + export DOTNET_INSTALL_DIR=`dirname "$_ResolvePath"` + fi + fi + + ReadGlobalVersion "dotnet" + local dotnet_sdk_version=$_ReadGlobalVersion + local dotnet_root="" + + # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, + # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues. + if [[ $global_json_has_runtimes == false && -n "${DOTNET_INSTALL_DIR:-}" && -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then + dotnet_root="$DOTNET_INSTALL_DIR" + else + dotnet_root="$repo_root/.dotnet" + + export DOTNET_INSTALL_DIR="$dotnet_root" + + if [[ ! -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then + if [[ "$install" == true ]]; then + InstallDotNetSdk "$dotnet_root" "$dotnet_sdk_version" + else + Write-PipelineTelemetryError -category 'InitializeToolset' "Unable to find dotnet with SDK version '$dotnet_sdk_version'" + ExitWithExitCode 1 + fi + fi + fi + + # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom + # build steps from using anything other than what we've downloaded. + Write-PipelinePrependPath -path "$dotnet_root" + + Write-PipelineSetVariable -name "DOTNET_MULTILEVEL_LOOKUP" -value "0" + Write-PipelineSetVariable -name "DOTNET_SKIP_FIRST_TIME_EXPERIENCE" -value "1" + + # return value + _InitializeDotNetCli="$dotnet_root" +} + +function InstallDotNetSdk { + local root=$1 + local version=$2 + local architecture="unset" + if [[ $# -ge 3 ]]; then + architecture=$3 + fi + InstallDotNet "$root" "$version" $architecture 'sdk' 'true' $runtime_source_feed $runtime_source_feed_key +} + +function InstallDotNet { + local root=$1 + local version=$2 + local runtime=$4 + + local dotnetVersionLabel="'$runtime v$version'" + if [[ -n "${4:-}" ]] && [ "$4" != 'sdk' ]; then + runtimePath="$root" + runtimePath="$runtimePath/shared" + case "$runtime" in + dotnet) + runtimePath="$runtimePath/Microsoft.NETCore.App" + ;; + aspnetcore) + runtimePath="$runtimePath/Microsoft.AspNetCore.App" + ;; + windowsdesktop) + runtimePath="$runtimePath/Microsoft.WindowsDesktop.App" + ;; + *) + ;; + esac + runtimePath="$runtimePath/$version" + + dotnetVersionLabel="runtime toolset '$runtime/$architecture v$version'" + + if [ -d "$runtimePath" ]; then + echo " Runtime toolset '$runtime/$architecture v$version' already installed." + local installSuccess=1 + return + fi + fi + + GetDotNetInstallScript "$root" + local install_script=$_GetDotNetInstallScript + + local installParameters=(--version $version --install-dir "$root") + + if [[ -n "${3:-}" ]] && [ "$3" != 'unset' ]; then + installParameters+=(--architecture $3) + fi + if [[ -n "${4:-}" ]] && [ "$4" != 'sdk' ]; then + installParameters+=(--runtime $4) + fi + if [[ "$#" -ge "5" ]] && [[ "$5" != 'false' ]]; then + installParameters+=(--skip-non-versioned-files) + fi + + local variations=() # list of variable names with parameter arrays in them + + local public_location=("${installParameters[@]}") + variations+=(public_location) + + local dotnetbuilds=("${installParameters[@]}" --azure-feed "https://dotnetbuilds.azureedge.net/public") + variations+=(dotnetbuilds) + + if [[ -n "${6:-}" ]]; then + variations+=(private_feed) + local private_feed=("${installParameters[@]}" --azure-feed $6) + if [[ -n "${7:-}" ]]; then + # The 'base64' binary on alpine uses '-d' and doesn't support '--decode' + # '-d'. To work around this, do a simple detection and switch the parameter + # accordingly. + decodeArg="--decode" + if base64 --help 2>&1 | grep -q "BusyBox"; then + decodeArg="-d" + fi + decodedFeedKey=`echo $7 | base64 $decodeArg` + private_feed+=(--feed-credential $decodedFeedKey) + fi + fi + + local installSuccess=0 + for variationName in "${variations[@]}"; do + local name="$variationName[@]" + local variation=("${!name}") + echo " Attempting to install $dotnetVersionLabel from $variationName." + bash "$install_script" "${variation[@]}" && installSuccess=1 + if [[ "$installSuccess" -eq 1 ]]; then + break + fi + + echo " Failed to install $dotnetVersionLabel from $variationName." + done + + if [[ "$installSuccess" -eq 0 ]]; then + Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to install $dotnetVersionLabel from any of the specified locations." + ExitWithExitCode 1 + fi +} + +function with_retries { + local maxRetries=5 + local retries=1 + echo "Trying to run '$@' for maximum of $maxRetries attempts." + while [[ $((retries++)) -le $maxRetries ]]; do + "$@" + + if [[ $? == 0 ]]; then + echo "Ran '$@' successfully." + return 0 + fi + + timeout=$((3**$retries-1)) + echo "Failed to execute '$@'. Waiting $timeout seconds before next attempt ($retries out of $maxRetries)." 1>&2 + sleep $timeout + done + + echo "Failed to execute '$@' for $maxRetries times." 1>&2 + + return 1 +} + +function GetDotNetInstallScript { + local root=$1 + local install_script="$root/dotnet-install.sh" + local install_script_url="https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh" + + if [[ ! -a "$install_script" ]]; then + mkdir -p "$root" + + echo "Downloading '$install_script_url'" + + # Use curl if available, otherwise use wget + if command -v curl > /dev/null; then + # first, try directly, if this fails we will retry with verbose logging + curl "$install_script_url" -sSL --retry 10 --create-dirs -o "$install_script" || { + if command -v openssl &> /dev/null; then + echo "Curl failed; dumping some information about dotnet.microsoft.com for later investigation" + echo | openssl s_client -showcerts -servername dotnet.microsoft.com -connect dotnet.microsoft.com:443 + fi + echo "Will now retry the same URL with verbose logging." + with_retries curl "$install_script_url" -sSL --verbose --retry 10 --create-dirs -o "$install_script" || { + local exit_code=$? + Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')." + ExitWithExitCode $exit_code + } + } + else + with_retries wget -v -O "$install_script" "$install_script_url" || { + local exit_code=$? + Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')." + ExitWithExitCode $exit_code + } + fi + fi + # return value + _GetDotNetInstallScript="$install_script" +} + +function InitializeBuildTool { + if [[ -n "${_InitializeBuildTool:-}" ]]; then + return + fi + + InitializeDotNetCli $restore + + # return values + _InitializeBuildTool="$_InitializeDotNetCli/dotnet" + _InitializeBuildToolCommand="msbuild" + _InitializeBuildToolFramework="net8.0" +} + +# Set RestoreNoCache as a workaround for https://github.com/NuGet/Home/issues/3116 +function GetNuGetPackageCachePath { + if [[ -z ${NUGET_PACKAGES:-} ]]; then + if [[ "$use_global_nuget_cache" == true ]]; then + export NUGET_PACKAGES="$HOME/.nuget/packages" + else + export NUGET_PACKAGES="$repo_root/.packages" + export RESTORENOCACHE=true + fi + fi + + # return value + _GetNuGetPackageCachePath=$NUGET_PACKAGES +} + +function InitializeNativeTools() { + if [[ -n "${DisableNativeToolsetInstalls:-}" ]]; then + return + fi + if grep -Fq "native-tools" $global_json_file + then + local nativeArgs="" + if [[ "$ci" == true ]]; then + nativeArgs="--installDirectory $tools_dir" + fi + "$_script_dir/init-tools-native.sh" $nativeArgs + fi +} + +function InitializeToolset { + if [[ -n "${_InitializeToolset:-}" ]]; then + return + fi + + GetNuGetPackageCachePath + + ReadGlobalVersion "Microsoft.DotNet.Arcade.Sdk" + + local toolset_version=$_ReadGlobalVersion + local toolset_location_file="$toolset_dir/$toolset_version.txt" + + if [[ -a "$toolset_location_file" ]]; then + local path=`cat "$toolset_location_file"` + if [[ -a "$path" ]]; then + # return value + _InitializeToolset="$path" + return + fi + fi + + if [[ "$restore" != true ]]; then + Write-PipelineTelemetryError -category 'InitializeToolset' "Toolset version $toolset_version has not been restored." + ExitWithExitCode 2 + fi + + local proj="$toolset_dir/restore.proj" + + local bl="" + if [[ "$binary_log" == true ]]; then + bl="/bl:$log_dir/ToolsetRestore.binlog" + fi + + echo '' > "$proj" + MSBuild-Core "$proj" $bl /t:__WriteToolsetLocation /clp:ErrorsOnly\;NoSummary /p:__ToolsetLocationOutputFile="$toolset_location_file" + + local toolset_build_proj=`cat "$toolset_location_file"` + + if [[ ! -a "$toolset_build_proj" ]]; then + Write-PipelineTelemetryError -category 'Build' "Invalid toolset path: $toolset_build_proj" + ExitWithExitCode 3 + fi + + # return value + _InitializeToolset="$toolset_build_proj" +} + +function ExitWithExitCode { + if [[ "$ci" == true && "$prepare_machine" == true ]]; then + StopProcesses + fi + exit $1 +} + +function StopProcesses { + echo "Killing running build processes..." + pkill -9 "dotnet" || true + pkill -9 "vbcscompiler" || true + return 0 +} + +function MSBuild { + local args=$@ + if [[ "$pipelines_log" == true ]]; then + InitializeBuildTool + InitializeToolset + + if [[ "$ci" == true ]]; then + export NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20 + export NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20 + Write-PipelineSetVariable -name "NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS" -value "20" + Write-PipelineSetVariable -name "NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS" -value "20" + fi + + local toolset_dir="${_InitializeToolset%/*}" + # new scripts need to work with old packages, so we need to look for the old names/versions + local selectedPath= + local possiblePaths=() + possiblePaths+=( "$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.ArcadeLogging.dll" ) + possiblePaths+=( "$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.Arcade.Sdk.dll" ) + possiblePaths+=( "$toolset_dir/netcoreapp2.1/Microsoft.DotNet.ArcadeLogging.dll" ) + possiblePaths+=( "$toolset_dir/netcoreapp2.1/Microsoft.DotNet.Arcade.Sdk.dll" ) + possiblePaths+=( "$toolset_dir/netcoreapp3.1/Microsoft.DotNet.ArcadeLogging.dll" ) + possiblePaths+=( "$toolset_dir/netcoreapp3.1/Microsoft.DotNet.Arcade.Sdk.dll" ) + possiblePaths+=( "$toolset_dir/net7.0/Microsoft.DotNet.ArcadeLogging.dll" ) + possiblePaths+=( "$toolset_dir/net7.0/Microsoft.DotNet.Arcade.Sdk.dll" ) + for path in "${possiblePaths[@]}"; do + if [[ -f $path ]]; then + selectedPath=$path + break + fi + done + if [[ -z "$selectedPath" ]]; then + Write-PipelineTelemetryError -category 'Build' "Unable to find arcade sdk logger assembly." + ExitWithExitCode 1 + fi + args+=( "-logger:$selectedPath" ) + fi + + MSBuild-Core ${args[@]} +} + +function MSBuild-Core { + if [[ "$ci" == true ]]; then + if [[ "$binary_log" != true && "$exclude_ci_binary_log" != true ]]; then + Write-PipelineTelemetryError -category 'Build' "Binary log must be enabled in CI build, or explicitly opted-out from with the -noBinaryLog switch." + ExitWithExitCode 1 + fi + + if [[ "$node_reuse" == true ]]; then + Write-PipelineTelemetryError -category 'Build' "Node reuse must be disabled in CI build." + ExitWithExitCode 1 + fi + fi + + InitializeBuildTool + + local warnaserror_switch="" + if [[ $warn_as_error == true ]]; then + warnaserror_switch="/warnaserror" + fi + + function RunBuildTool { + export ARCADE_BUILD_TOOL_COMMAND="$_InitializeBuildTool $@" + + "$_InitializeBuildTool" "$@" || { + local exit_code=$? + # We should not Write-PipelineTaskError here because that message shows up in the build summary + # The build already logged an error, that's the reason it failed. Producing an error here only adds noise. + echo "Build failed with exit code $exit_code. Check errors above." + + # When running on Azure Pipelines, override the returned exit code to avoid double logging. + if [[ "$ci" == "true" && -n ${SYSTEM_TEAMPROJECT:-} ]]; then + Write-PipelineSetResult -result "Failed" -message "msbuild execution failed." + # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error + # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error + ExitWithExitCode 0 + else + ExitWithExitCode $exit_code + fi + } + } + + RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" +} + +function GetDarc { + darc_path="$temp_dir/darc" + version="$1" + + if [[ -n "$version" ]]; then + version="--darcversion $version" + fi + + "$eng_root/common/darc-init.sh" --toolpath "$darc_path" $version +} + +ResolvePath "${BASH_SOURCE[0]}" +_script_dir=`dirname "$_ResolvePath"` + +. "$_script_dir/pipeline-logging-functions.sh" + +eng_root=`cd -P "$_script_dir/.." && pwd` +repo_root=`cd -P "$_script_dir/../.." && pwd` +repo_root="${repo_root}/" +artifacts_dir="${repo_root}artifacts" +toolset_dir="$artifacts_dir/toolset" +tools_dir="${repo_root}.tools" +log_dir="$artifacts_dir/log/$configuration" +temp_dir="$artifacts_dir/tmp/$configuration" + +global_json_file="${repo_root}global.json" +# determine if global.json contains a "runtimes" entry +global_json_has_runtimes=false +if command -v jq &> /dev/null; then + if jq -e '.tools | has("runtimes")' "$global_json_file" &> /dev/null; then + global_json_has_runtimes=true + fi +elif [[ "$(cat "$global_json_file")" =~ \"runtimes\"[[:space:]\:]*\{ ]]; then + global_json_has_runtimes=true +fi + +# HOME may not be defined in some scenarios, but it is required by NuGet +if [[ -z $HOME ]]; then + export HOME="${repo_root}artifacts/.home/" + mkdir -p "$HOME" +fi + +mkdir -p "$toolset_dir" +mkdir -p "$temp_dir" +mkdir -p "$log_dir" + +Write-PipelineSetVariable -name "Artifacts" -value "$artifacts_dir" +Write-PipelineSetVariable -name "Artifacts.Toolset" -value "$toolset_dir" +Write-PipelineSetVariable -name "Artifacts.Log" -value "$log_dir" +Write-PipelineSetVariable -name "Temp" -value "$temp_dir" +Write-PipelineSetVariable -name "TMP" -value "$temp_dir" + +# Import custom tools configuration, if present in the repo. +if [ -z "${disable_configure_toolset_import:-}" ]; then + configure_toolset_script="$eng_root/configure-toolset.sh" + if [[ -a "$configure_toolset_script" ]]; then + . "$configure_toolset_script" + fi +fi + +# TODO: https://github.com/dotnet/arcade/issues/1468 +# Temporary workaround to avoid breaking change. +# Remove once repos are updated. +if [[ -n "${useInstalledDotNetCli:-}" ]]; then + use_installed_dotnet_cli="$useInstalledDotNetCli" +fi diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/dcppack/Aspire.Hosting.Orchestration.props dotnet8-8.0.100-8.0.0/src/aspire/eng/dcppack/Aspire.Hosting.Orchestration.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/dcppack/Aspire.Hosting.Orchestration.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/dcppack/Aspire.Hosting.Orchestration.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,2 @@ + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/dcppack/Aspire.Hosting.Orchestration.targets dotnet8-8.0.100-8.0.0/src/aspire/eng/dcppack/Aspire.Hosting.Orchestration.targets --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/dcppack/Aspire.Hosting.Orchestration.targets 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/dcppack/Aspire.Hosting.Orchestration.targets 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ + + + + $([MSBuild]::NormalizePath($(MSBuildThisFileDirectory), '..', 'tools')) + $([MSBuild]::NormalizePath($(DcpDir), 'ext')) + $([MSBuild]::NormalizePath($(DcpExtensionsPath), 'bin')) + $([MSBuild]::NormalizePath($(DcpDir), 'dcp')) + $(DcpCliPath).exe + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/dcppack/dcppack.csproj dotnet8-8.0.100-8.0.0/src/aspire/eng/dcppack/dcppack.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/dcppack/dcppack.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/dcppack/dcppack.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,55 @@ + + + + + + + net8.0 + true + true + true + false + $(ArtifactsShippingPackagesDir) + + + + win-x64 + $([System.String]::Copy('$(DcpRuntime)').Replace('win-', 'windows-').Replace('osx-', 'darwin-').Replace('-x86', '-386').Replace('-x64', '-amd64')) + Windows + Unix + + + + Aspire.Hosting.Orchestration.$(DcpRuntime) + .NET Aspire Orchestration Dependencies + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/dcppack/Sdk.in.targets dotnet8-8.0.100-8.0.0/src/aspire/eng/dcppack/Sdk.in.targets --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/dcppack/Sdk.in.targets 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/dcppack/Sdk.in.targets 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/dcppack/Sdk.props dotnet8-8.0.100-8.0.0/src/aspire/eng/dcppack/Sdk.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/dcppack/Sdk.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/dcppack/Sdk.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,2 @@ + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/dcppack/UnixFilePermissions.xml dotnet8-8.0.100-8.0.0/src/aspire/eng/dcppack/UnixFilePermissions.xml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/dcppack/UnixFilePermissions.xml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/dcppack/UnixFilePermissions.xml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/pipelines/azure-pipelines-codeql.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/pipelines/azure-pipelines-codeql.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/pipelines/azure-pipelines-codeql.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/pipelines/azure-pipelines-codeql.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,63 @@ +parameters: + # Optionally do not publish to TSA. Useful for e.g. verifying fixes before PR. +- name: TSAEnabled + displayName: Publish results to TSA + type: boolean + default: true + +variables: +- template: /eng/pipelines/common-variables.yml +- template: /eng/common/templates/variables/pool-providers.yml + # CG is handled in the primary CI pipeline +- name: skipComponentGovernanceDetection + value: true + # Force CodeQL enabled so it may be run on any branch +- name: Codeql.Enabled + value: true + # Do not let CodeQL 3000 Extension gate scan frequency +- name: Codeql.Cadence + value: 0 + # CodeQL needs this plumbed along as a variable to enable TSA +- name: Codeql.TSAEnabled + value: ${{ parameters.TSAEnabled }} + + # Build variables +- name: _BuildConfig + value: Release + +trigger: none + +schedules: + - cron: 0 12 * * 1 + displayName: Weekly Monday CodeQL run + branches: + include: + - main + - release/* + always: true + +jobs: +- job: codeql + displayName: CodeQL + pool: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals 1es-windows-2022 + timeoutInMinutes: 90 + + steps: + + - task: UseDotNet@2 + inputs: + useGlobalJson: true + + - task: CodeQL3000Init@0 + displayName: CodeQL Initialize + + - script: eng\common\cibuild.cmd + -configuration $(_BuildConfig) + -prepareMachine + /p:Test=false + displayName: Windows Build + + - task: CodeQL3000Finalize@0 + displayName: CodeQL Finalize \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/pipelines/azure-pipelines.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/pipelines/azure-pipelines.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/pipelines/azure-pipelines.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/pipelines/azure-pipelines.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,156 @@ +trigger: + batch: true + branches: + include: + - main + - release/* + - internal/release/* + +pr: + branches: + include: + - main + - release/* + - internal/release/* + +variables: +- template: /eng/pipelines/common-variables.yml +- template: /eng/common/templates/variables/pool-providers.yml + +- ${{ if eq(variables['_RunAsPublic'], 'true') }}: + - name: _AdditionalBuildArgs + value: '' + - name: _BuildJobDisplayName + value: 'Build and Test' +- ${{ else }}: + - name: _AdditionalBuildArgs + value: '/p:Test=false' + - name: _BuildJobDisplayName + value: 'Build, Sign and Publish' + +resources: + containers: + - container: LinuxContainer + image: mcr.microsoft.com/dotnet-buildtools/prereqs:cbl-mariner-2.0-fpm + +stages: +- stage: build + displayName: Build + jobs: + # TODO: Temporarily disabled, see:https://github.com/dotnet/aspire/issues/145 + # - ${{ if and(eq(variables._RunAsInternal, True), notin(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) }}: + # - template: /eng/common/templates/job/onelocbuild.yml + # parameters: + # MirrorRepo: aspire + # LclSource: lclFilesFromPackage + # LclPackageId: 'LCL-JUNO-PROD-ASPIRE' + - template: /eng/common/templates/jobs/jobs.yml + parameters: + artifacts: + publish: + artifacts: false + logs: true + manifests: true + enableMicrobuild: true + enablePublishUsingPipelines: true + publishAssetsImmediately: true + enablePublishTestResults: true + enableSourceBuild: true + testResultsFormat: vstest + enableSourceIndex: false + workspace: + clean: all + jobs: + - job: windows + timeoutInMinutes: 60 + pool: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals 1es-windows-2022 + strategy: + matrix: + release: + _BuildConfig: Release + preSteps: + - checkout: self + fetchDepth: 0 + clean: true + steps: + - task: PowerShell@2 + displayName: Setup Private Feeds Credentials + condition: eq(variables['Agent.OS'], 'Windows_NT') + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 + arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - script: eng\common\cibuild.cmd + -configuration $(_BuildConfig) + -prepareMachine + $(_AdditionalBuildArgs) + $(_InternalBuildArgs) + displayName: $(_BuildJobDisplayName) + - script: eng\common\cibuild.cmd + -configuration $(_BuildConfig) + -projects eng\workloads\workloads.csproj + $(_AdditionalBuildArgs) + $(_InternalBuildArgs) + displayName: Build Workloads + - task: PublishBuildArtifacts@1 + displayName: Publish Package Artifacts + inputs: + pathToPublish: '$(Build.SourcesDirectory)/artifacts/packages' + artifactName: PackageArtifacts + artifactType: container + - task: PublishBuildArtifacts@1 + displayName: Publish VSDrop MSIs + inputs: + pathToPublish: '$(Build.SourcesDirectory)/artifacts/VSSetup/$(_BuildConfig)' + artifactName: VSDropInsertion + artifactType: container + + - ${{ if eq(variables._RunAsPublic, True) }}: + - job: linux + timeoutInMinutes: 60 + container: LinuxContainer + pool: + vmImage: ubuntu-latest + strategy: + matrix: + debug: + _BuildConfig: Debug + preSteps: + - checkout: self + clean: true + steps: + - task: Bash@3 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + arguments: $(Build.SourcesDirectory)/NuGet.config $Token + condition: ne(variables['Agent.OS'], 'Windows_NT') + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - script: eng/common/cibuild.sh + --configuration $(_BuildConfig) + --prepareMachine + $(_AdditionalBuildArgs) + $(_InternalBuildArgs) + displayName: $(_BuildJobDisplayName) + +- ${{ if eq(variables._RunAsInternal, True) }}: + - template: /eng/common/templates/post-build/post-build.yml + parameters: + publishAssetsImmediately: true + SDLValidationParameters: + enable: false + params: ' -SourceToolsList $(_TsaSourceToolsList) + -TsaInstanceURL $(_TsaInstanceURL) + -TsaProjectName $(_TsaProjectName) + -TsaNotificationEmail $(_TsaNotificationEmail) + -TsaCodebaseAdmin $(_TsaCodebaseAdmin) + -TsaBugAreaPath $(_TsaBugAreaPath) + -TsaIterationPath $(_TsaIterationPath) + -TsaRepositoryName $(_TsaRepositoryName) + -TsaCodebaseName $(_TsaCodebaseName) + -TsaOnboard $(_TsaOnboard) + -TsaPublish $(_TsaPublish)' diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/pipelines/common-variables.yml dotnet8-8.0.100-8.0.0/src/aspire/eng/pipelines/common-variables.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/pipelines/common-variables.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/pipelines/common-variables.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,32 @@ +variables: + - name: _TeamName + value: dotnet-aspire + - name: HelixApiAccessToken + value: '' + - name: _RunAsPublic + value: True + - name: _RunAsInternal + value: False + - name: _InternalBuildArgs + value: '' + + - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - name: _RunAsPublic + value: False + - name: _RunAsInternal + value: True + - name: _SignType + value: real + # DotNet-Blob-Feed provides: dotnetfeed-storage-access-key-1 + # Publish-Build-Assets provides: MaestroAccessToken, BotAccount-dotnet-maestro-bot-PAT + # DotNet-HelixApi-Access provides: HelixApiAccessToken + - group: Publish-Build-Assets + - group: DotNet-HelixApi-Access + - group: SDL_Settings + - name: _InternalBuildArgs + value: /p:DotNetSignType=$(_SignType) + /p:TeamName=$(_TeamName) + /p:DotNetPublishUsingPipelines=true + /p:OfficialBuildId=$(BUILD.BUILDNUMBER) + - name: PostBuildSign + value: false \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/Publishing.props dotnet8-8.0.100-8.0.0/src/aspire/eng/Publishing.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/Publishing.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/Publishing.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,41 @@ + + + 3 + + false + + + + <_UploadPathRoot>aspire + + + + $(PublishDependsOnTargets);_PublishInstallers + + + + <_InstallersToPublish Include="$(ArtifactsDir)**\*.wixpack.zip" Condition="'$(PostBuildSign)' == 'true'" /> + <_InstallerManifestFilesToPublish Include="$(ArtifactsDir)VSSetup\$(Configuration)\Insertion\**\*.zip" /> + + + + + + + + + + true + true + $(_UploadPathRoot)/$(_PackageVersion)/%(Filename)%(Extension) + + + true + true + $(_UploadPathRoot)/$(_PackageVersion)/%(Filename)%(Extension) + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/ReplaceText.targets dotnet8-8.0.100-8.0.0/src/aspire/eng/ReplaceText.targets --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/ReplaceText.targets 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/ReplaceText.targets 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_TextReplacementFile Include="@(None->WithMetadataValue('PerformTextReplacement', 'True'))"> + %(RelativeDir)$([System.String]::Copy('%(Filename)%(Extension)').Replace('.in.', '.')) + <_TextReplacementIntermediateFile>$(IntermediateOutputPath)TextReplacement\%(RelativeDir)$([System.String]::Copy('%(Filename)%(Extension)').Replace('.in.', '.')) + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/.runsettings dotnet8-8.0.100-8.0.0/src/aspire/eng/.runsettings --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/.runsettings 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/.runsettings 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,18 @@ + + + + + category!=failing + + + + + + + + Minimal + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/Signing.props dotnet8-8.0.100-8.0.0/src/aspire/eng/Signing.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/Signing.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/Signing.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/SourceBuildPrebuiltBaseline.xml dotnet8-8.0.100-8.0.0/src/aspire/eng/SourceBuildPrebuiltBaseline.xml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/SourceBuildPrebuiltBaseline.xml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/SourceBuildPrebuiltBaseline.xml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/SourceBuild.props dotnet8-8.0.100-8.0.0/src/aspire/eng/SourceBuild.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/SourceBuild.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/SourceBuild.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ + + + + aspire + true + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/Version.Details.xml dotnet8-8.0.100-8.0.0/src/aspire/eng/Version.Details.xml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/Version.Details.xml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/Version.Details.xml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,78 @@ + + + + + https://github.com/microsoft/usvc-apiserver + 7a020a231e5b13608ab3749d30263269ff32984d + + + https://github.com/microsoft/usvc-apiserver + 7a020a231e5b13608ab3749d30263269ff32984d + + + https://github.com/microsoft/usvc-apiserver + 7a020a231e5b13608ab3749d30263269ff32984d + + + https://github.com/microsoft/usvc-apiserver + 7a020a231e5b13608ab3749d30263269ff32984d + + + https://github.com/microsoft/usvc-apiserver + 7a020a231e5b13608ab3749d30263269ff32984d + + + https://github.com/microsoft/usvc-apiserver + 7a020a231e5b13608ab3749d30263269ff32984d + + + https://github.com/microsoft/usvc-apiserver + 7a020a231e5b13608ab3749d30263269ff32984d + + + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions + 0db4832a73bcd20fe6eaaa01ff64d18e329d438d + + + https://github.com/dotnet/source-build-reference-packages + b4fa7f2e1e65ef49881be2ab2df27624280a8c55 + + + + + + https://github.com/dotnet/arcade + a57022b44f3ff23de925530ea1d27da9701aed57 + + + + https://github.com/dotnet/arcade + a57022b44f3ff23de925530ea1d27da9701aed57 + + + https://github.com/dotnet/arcade + a57022b44f3ff23de925530ea1d27da9701aed57 + + + https://github.com/dotnet/arcade + a57022b44f3ff23de925530ea1d27da9701aed57 + + + https://github.com/dotnet/arcade + a57022b44f3ff23de925530ea1d27da9701aed57 + + + https://github.com/dotnet/arcade + a57022b44f3ff23de925530ea1d27da9701aed57 + + + https://github.com/dotnet/arcade + a57022b44f3ff23de925530ea1d27da9701aed57 + + + https://github.com/dotnet/xliff-tasks + 73f0850939d96131c28cf6ea6ee5aacb4da0083a + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/Versions.dev.targets dotnet8-8.0.100-8.0.0/src/aspire/eng/Versions.dev.targets --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/Versions.dev.targets 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/Versions.dev.targets 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ + + + + + $(PackageVersion) + $(PackageVersion) + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/Versions.props dotnet8-8.0.100-8.0.0/src/aspire/eng/Versions.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/Versions.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/Versions.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,29 @@ + + + + 8 + 0 + 0 + 8.0.0 + preview.1 + true + + + + 8.0.100-rtm.23512.16 + 0.1.41 + 0.1.41 + 0.1.41 + 0.1.41 + 0.1.41 + 0.1.41 + 0.1.41 + 8.0.0-rc.1.23419.3 + 13.3.8825-net8-rc1 + 8.0.0-beta.23525.4 + 8.0.0-beta.23525.4 + 8.0.0-beta.23525.4 + 8.0.0-beta.23525.4 + 8.0.0 + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/Versions.targets dotnet8-8.0.100-8.0.0/src/aspire/eng/Versions.targets --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/Versions.targets 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/Versions.targets 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,119 @@ + + + nightly + [a-z]+\.[0-9] + + + $(SYSTEM_PULLREQUEST_TARGETBRANCH) + $(BUILD_SOURCEBRANCHNAME) + + + + + SetVersions; + VersionInfoReport; + $(CoreCompileDependsOn); + + + SetVersions; + VersionInfoReport; + $(GenerateNuspecDependsOn); + + + SetVersions; + VersionInfoReport; + $(GetPackageVersionDependsOn); + + + GetAssemblyVersion; + $(SetVersionsBefore); + + + GitInfo; + GitVersion; + $(SetVersionsDependsOn); + + + GitInfo; + GitVersion; + + + + + + main + true + true + + + + + $([System.Text.RegularExpressions.Regex]::Match($(GitTag), $(RegexTag))) + main + true + true + $(SemVerLabel) + $(NightlyTag) + -$(GitSemVerLabel) + + + + + + + + + + + + + @(VersionMetadata -> '%(Identity)', '-') + +$(VersionMetadataLabel) + $(GitBaseVersionMajor).$(GitBaseVersionMinor).$(GitBaseVersionPatch) + $(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch)$(GitSemVerDashLabel) + $(GitSemVerMajor).$(GitSemVerMinor).$(GitBaseVersionPatch)$(GitSemVerDashLabel).$(BUILDVERSION) + $(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch) + $(GitSemVerMajor).$(GitSemVerMinor).$(GitBaseVersionPatch) + $(GitSemVerMajor).$(GitSemVerMinor).$(GitBaseVersionPatch).$(BUILDVERSION) + $(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch).0 + $(PackageReferenceVersion)$(VersionMetadataPlusLabel) + + + + $(PackageVersion) + $(Version).$(GitCommits) + 1.0.0.0 + + + + + <_Parameter1>Version + <_Parameter2>1.0.0.0 + + + <_Parameter1>PackageVersion + <_Parameter2>$(PackageVersion) + + + + + + + + + + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/workloads/aspire.vsmanproj dotnet8-8.0.100-8.0.0/src/aspire/eng/workloads/aspire.vsmanproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/workloads/aspire.vsmanproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/workloads/aspire.vsmanproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,67 @@ + + + + + false + false + false + build-manifest + true + false + DotNetOptionalWorkloads + vs + 42.42.42 + true + $(ManifestOutputPath) + + + + + / + + + /CHS/ + + + /CHT/ + + + /CSY/ + + + /DEU/ + + + /ENU/ + + + /ESN/ + + + /FRA/ + + + /ITA/ + + + /JPN/ + + + /KOR/ + + + /PLK/ + + + /PTB/ + + + /RUS/ + + + /TRK/ + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/workloads/workloads.csproj dotnet8-8.0.100-8.0.0/src/aspire/eng/workloads/workloads.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/eng/workloads/workloads.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/eng/workloads/workloads.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,204 @@ + + + + + net8.0 + false + false + + $(ArtifactsObjDir)workloads/ + $(WorkloadIntermediateOutputPath)VS/ + $(ArtifactsBinDir)workloads/ + $(workloadArtifactsPath)/ + $(ArtifactsShippingPackagesDir) + $(workloadPackagesPath)/ + false + + + + + + $(ArtifactsObjDir)/LightCommandPackages + + $(ArtifactsNonShippingPackagesDir) + + + + $(PkgMicrosoft_Signed_Wix)\tools + $(PkgMicroBuild_Plugins_SwixBuild_Dotnet) + $(SwixPluginPath)\build\MicroBuild.Plugins.SwixBuild.targets + + + + + + + + + + + + <_DcpRuntimes Include="win-x86" /> + <_DcpRuntimes Include="win-x64" /> + <_DcpRuntimes Include="win-arm64" /> + <_DcpRuntimes Include="linux-x64" /> + <_DcpRuntimes Include="linux-arm64" /> + <_DcpRuntimes Include="osx-x64" /> + <_DcpRuntimes Include="osx-arm64" /> + + + + + + + + <_ComponentResources Include="aspire" Title=".NET Aspire SDK (Preview)" + Description=".NET Aspire SDK (Preview)" + AdvertisePackage="true"/> + <_ComponentResources Include="aspire-host-runtime" Title=".NET Aspire SDK Hosts" + Description=".NET Aspire SDK Hosts"/> + + + + + + $(VisualStudioSetupInsertionPath)$(SDKBundleVersion)\ + + + + + + + Aspire. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_WixObj Include="$(_WixObjDir)\**\*.wixobj" /> + + + + + + + + + + + + + + + + + + + <_MajorVersion>$([System.Version]::Parse('$(AssemblyVersion)').Major) + <_MinorVersion>$([System.Version]::Parse('$(AssemblyVersion)').Minor) + <_PatchVersion>$([System.Version]::Parse('$(AssemblyVersion)').Build) + <_BuildNumber>$([System.Version]::Parse('$(AssemblyVersion)').Revision) + + + + + + 5 + + 1996-04-01 + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/.gitattributes dotnet8-8.0.100-8.0.0/src/aspire/.gitattributes --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/.gitattributes 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/.gitattributes 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,57 @@ +# Set default behavior to automatically normalize line endings. +* text=auto + + +*.jpg binary +*.png binary +*.gif binary + +# Force bash scripts to always use lf line endings so that if a repo is accessed +# in Unix via a file share from Windows, the scripts will work. +*.in text eol=lf +*.sh text eol=lf + +# Likewise, force cmd and batch scripts to always use crlf +*.cmd text eol=crlf +*.bat text eol=crlf + +*.cs text=auto diff=csharp +*.vb text=auto +*.resx text=auto +*.c text=auto +*.cpp text=auto +*.cxx text=auto +*.h text=auto +*.hxx text=auto +*.py text=auto +*.rb text=auto +*.java text=auto +*.html text=auto +*.htm text=auto +*.css text=auto +*.scss text=auto +*.sass text=auto +*.less text=auto +*.js text=auto +*.lisp text=auto +*.clj text=auto +*.sql text=auto +*.php text=auto +*.lua text=auto +*.m text=auto +*.asm text=auto +*.erl text=auto +*.fs text=auto +*.fsx text=auto +*.hs text=auto + +*.csproj text=auto +*.vbproj text=auto +*.fsproj text=auto +*.dbproj text=auto +*.sln text=auto eol=crlf + +# Set linguist language for .h files explicitly based on +# https://github.com/github/linguist/issues/1626#issuecomment-401442069 +# this only affects the repo's language statistics +*.h linguist-language=C diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/.gitignore dotnet8-8.0.100-8.0.0/src/aspire/.gitignore --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/.gitignore 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,138 @@ +syntax: glob + +### VisualStudio ### + +# Tools directory +.dotnet/ +.packages/ +.tools/ + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# Build results + +artifacts/ +artifacts_stage_1/ +[Dd]ebug/ +[Rr]elease/ +x64/ !eng/common/cross/x64/ +x86/ !eng/common/cross/x86/ +[Bb]in/ +[Oo]bj/ +msbuild.log +msbuild.err +msbuild.wrn +*.binlog + +# Visual Studio 2015 +.vs/ + +# Visual Studio 2015 Pre-CTP6 +*.sln.ide +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# DotCover is a Code Coverage Tool +*.dotCover + +# NuGet Packages +*.nuget.props +*.nuget.targets +*.nupkg +**/packages/* + +### Windows ### + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Linux ### + +*~ + +# KDE directory preferences +.directory + +### OSX ### + +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# vim temporary files +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist +*~ + +# Visual Studio Code +.vscode/ + +# Private test configuration and binaries. +config.ps1 +**/IISApplications + + +# Node.js modules +node_modules/ + +# Python Compile Outputs + +*.pyc + +# IntelliJ +.idea/ + +# vscode python env files +.env diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/global.json dotnet8-8.0.100-8.0.0/src/aspire/global.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/global.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/global.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,16 @@ +{ + "sdk": { + "version": "8.0.100-rtm.23512.16", + "rollForward": "latestPatch", + "allowPrerelease": true + }, + "tools": { + "dotnet": "8.0.100-rtm.23512.16" + }, + "msbuild-sdks": { + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.23525.4", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.23525.4", + "Microsoft.DotNet.SharedFramework.Sdk": "8.0.0-beta.23463.1", + "Microsoft.Build.NoTargets": "3.7.0" + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/LICENSE.TXT dotnet8-8.0.100-8.0.0/src/aspire/LICENSE.TXT --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/LICENSE.TXT 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/LICENSE.TXT 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/NuGet.config dotnet8-8.0.100-8.0.0/src/aspire/NuGet.config --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/NuGet.config 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/NuGet.config 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/README.md dotnet8-8.0.100-8.0.0/src/aspire/README.md --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/README.md 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/README.md 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,13 @@ +# .NET Aspire + +## Getting started with Aspire + +Follow instructions in [docs/getting-started.md](docs/getting-started.md) to get started using .NET Aspire. + +## Running the sample application + +Follow instructions in [docs/repo-instructions.md](docs/repo-instructions.md) for working in the code in the repository. + +## Contributing + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/restore.cmd dotnet8-8.0.100-8.0.0/src/aspire/restore.cmd --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/restore.cmd 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/restore.cmd 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,3 @@ +@ECHO OFF +SETLOCAL +PowerShell -NoProfile -NoLogo -ExecutionPolicy ByPass -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0\eng\build.ps1' -restore %*; exit $LASTEXITCODE" diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/AppHost/appsettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/AppHost/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/AppHost/appsettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/AppHost/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/AppHost/aspire-manifest.json dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/AppHost/aspire-manifest.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/AppHost/aspire-manifest.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/AppHost/aspire-manifest.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,86 @@ +{ + "resources": { + "servicea": { + "type": "project.v1", + "path": "..\\ServiceA\\DaprServiceA.csproj", + "env": { + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true", + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true" + }, + "bindings": { + "http": { + "scheme": "http", + "protocol": "tcp", + "transport": "http" + }, + "https": { + "scheme": "https", + "protocol": "tcp", + "transport": "http" + } + } + }, + "serviceb": { + "type": "project.v1", + "path": "..\\ServiceB\\DaprServiceB.csproj", + "env": { + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true", + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true" + }, + "bindings": { + "http": { + "scheme": "http", + "protocol": "tcp", + "transport": "http" + }, + "https": { + "scheme": "https", + "protocol": "tcp", + "transport": "http" + } + } + }, + "service-a": { + "type": "executable.v1", + "env": {}, + "bindings": { + "grpc": { + "scheme": "tcp", + "protocol": "tcp", + "transport": "tcp" + }, + "http": { + "scheme": "tcp", + "protocol": "tcp", + "transport": "tcp" + }, + "metrics": { + "scheme": "tcp", + "protocol": "tcp", + "transport": "tcp" + } + } + }, + "service-b": { + "type": "executable.v1", + "env": {}, + "bindings": { + "grpc": { + "scheme": "tcp", + "protocol": "tcp", + "transport": "tcp" + }, + "http": { + "scheme": "tcp", + "protocol": "tcp", + "transport": "tcp" + }, + "metrics": { + "scheme": "tcp", + "protocol": "tcp", + "transport": "tcp" + } + } + } + } +} \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/AppHost/DaprAppHost.csproj dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/AppHost/DaprAppHost.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/AppHost/DaprAppHost.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/AppHost/DaprAppHost.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + true + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/AppHost/Directory.Build.props dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/AppHost/Directory.Build.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/AppHost/Directory.Build.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/AppHost/Directory.Build.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/AppHost/Directory.Build.targets dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/AppHost/Directory.Build.targets --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/AppHost/Directory.Build.targets 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/AppHost/Directory.Build.targets 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/AppHost/Program.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/AppHost/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/AppHost/Program.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/AppHost/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,13 @@ +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddDapr(); + +builder.AddProject("servicea") + .WithDaprSidecar("service-a"); + +builder.AddProject("serviceb") + .WithDaprSidecar("service-b"); + +using var app = builder.Build(); + +await app.RunAsync(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/AppHost/Properties/launchSettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/AppHost/Properties/launchSettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/AppHost/Properties/launchSettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/AppHost/Properties/launchSettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,39 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:15887", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16032" + } + }, + "generate-manifest": { + "commandName": "Project", + "launchBrowser": true, + "dotnetRunMessages": true, + "commandLineArgs": "--publisher manifest --output-path aspire-manifest.json", + "applicationUrl": "http://localhost:15887", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16031" + } + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:17887;http://localhost:15887", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:18032" + } + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json" +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceA/appsettings.Development.json dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceA/appsettings.Development.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceA/appsettings.Development.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceA/appsettings.Development.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceA/appsettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceA/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceA/appsettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceA/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceA/DaprServiceA.csproj dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceA/DaprServiceA.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceA/DaprServiceA.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceA/DaprServiceA.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceA/Program.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceA/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceA/Program.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceA/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Dapr.Client; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +// Add services to the container. +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddDaprClient(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +var summaries = new[] +{ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +}; + +app.MapGet("/weatherforecast", (DaprClient client) => +{ + return client.InvokeMethodAsync(HttpMethod.Get, "service-b", "weatherforecast"); +}) +.WithName("GetWeatherForecast") +.WithOpenApi(); + +app.MapDefaultEndpoints(); + +app.Run(); + +internal sealed record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceA/Properties/launchSettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceA/Properties/launchSettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceA/Properties/launchSettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceA/Properties/launchSettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:49082", + "sslPort": 44300 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5048", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7077;http://localhost:5048", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceA/ServiceA.http dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceA/ServiceA.http --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceA/ServiceA.http 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceA/ServiceA.http 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,6 @@ +@ServiceA_HostAddress = http://localhost:5048 + +GET {{ServiceA_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceB/appsettings.Development.json dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceB/appsettings.Development.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceB/appsettings.Development.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceB/appsettings.Development.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceB/appsettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceB/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceB/appsettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceB/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceB/DaprServiceB.csproj dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceB/DaprServiceB.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceB/DaprServiceB.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceB/DaprServiceB.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceB/Program.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceB/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceB/Program.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceB/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +// Add services to the container. +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +var summaries = new[] +{ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +}; + +app.MapGet("/weatherforecast", () => +{ + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + return forecast; +}) +.WithName("GetWeatherForecast") +.WithOpenApi(); + +app.MapDefaultEndpoints(); + +app.Run(); + +internal sealed record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceB/Properties/launchSettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceB/Properties/launchSettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceB/Properties/launchSettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceB/Properties/launchSettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:42757", + "sslPort": 44335 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5292", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7059;http://localhost:5292", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceB/ServiceB.http dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceB/ServiceB.http --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/dapr/ServiceB/ServiceB.http 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/dapr/ServiceB/ServiceB.http 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,6 @@ +@ServiceB_HostAddress = http://localhost:5292 + +GET {{ServiceB_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/Directory.Build.props dotnet8-8.0.100-8.0.0/src/aspire/samples/Directory.Build.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/Directory.Build.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/Directory.Build.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,21 @@ + + + + + + + $(NoWarn),1573,1591,1712 + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/.editorconfig dotnet8-8.0.100-8.0.0/src/aspire/samples/.editorconfig --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/.editorconfig 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/.editorconfig 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,7 @@ +[*.{cs,vb}] + +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = silent + +# IDE0073: File header +dotnet_diagnostic.IDE0073.severity = silent diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/ApiGateway/ApiGateway.csproj dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/ApiGateway/ApiGateway.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/ApiGateway/ApiGateway.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/ApiGateway/ApiGateway.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,22 @@ + + + + net8.0 + localhost:5001 + api-gateway + mcr.microsoft.com/dotnet/aspnet:8.0-preview + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/ApiGateway/appsettings.Development.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/ApiGateway/appsettings.Development.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/ApiGateway/appsettings.Development.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/ApiGateway/appsettings.Development.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/ApiGateway/appsettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/ApiGateway/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/ApiGateway/appsettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/ApiGateway/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,49 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ReverseProxy": { + "Routes": { + "catalog": { + "ClusterId": "catalog", + "Match": { + "Path": "/catalog/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/catalog" } + ] + }, + "basket": { + "ClusterId": "basket", + "Match": { + "Path": "/basket/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/basket" } + ] + } + }, + "Clusters": { + "catalog": { + "Destinations": { + "catalog": { + "Address": "http://catalogservice", + "Health": "http://catalogservice/readiness" + } + } + }, + "basket": { + "Destinations": { + "basket": { + "Address": "https://basketservice", + "Health": "http://basketservice/readiness" + } + } + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/ApiGateway/Program.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/ApiGateway/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/ApiGateway/Program.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/ApiGateway/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,12 @@ +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); +builder.Services.AddReverseProxy() + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")) + .AddServiceDiscoveryDestinationResolver(); + +var app = builder.Build(); + +app.MapReverseProxy(); + +app.Run(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/ApiGateway/Properties/launchSettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/ApiGateway/Properties/launchSettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/ApiGateway/Properties/launchSettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/ApiGateway/Properties/launchSettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,23 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5130", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7106;http://localhost:5130", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/AppHost/AppHost.csproj dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/AppHost/AppHost.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/AppHost/AppHost.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/AppHost/AppHost.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,23 @@ + + + + Exe + net8.0 + 1bf0740a-0dfc-45aa-9002-def9b2b17da0 + enable + true + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/AppHost/appsettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/AppHost/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/AppHost/appsettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/AppHost/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/AppHost/aspire-manifest.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/AppHost/aspire-manifest.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/AppHost/aspire-manifest.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/AppHost/aspire-manifest.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,152 @@ +{ + "resources": { + "postgres": { + "type": "postgres.server.v1" + }, + "catalogdb": { + "type": "postgres.database.v1", + "parent": "postgres" + }, + "basketcache": { + "type": "redis.v1" + }, + "catalogservice": { + "type": "project.v1", + "path": "..\\CatalogService\\CatalogService.csproj", + "env": { + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true", + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true", + "ConnectionStrings__catalogdb": "{catalogdb.connectionString}" + }, + "bindings": { + "http": { + "scheme": "http", + "protocol": "tcp", + "transport": "http" + }, + "https": { + "scheme": "https", + "protocol": "tcp", + "transport": "http" + } + } + }, + "messaging": { + "type": "azure.servicebus.v1", + "queues": [ + "orders" + ] + }, + "basketservice": { + "type": "project.v1", + "path": "..\\BasketService\\BasketService.csproj", + "env": { + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true", + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true", + "ConnectionStrings__basketcache": "{basketcache.connectionString}", + "ConnectionStrings__messaging": "{messaging.connectionString}" + }, + "bindings": { + "http": { + "scheme": "http", + "protocol": "tcp", + "transport": "http2" + }, + "https": { + "scheme": "https", + "protocol": "tcp", + "transport": "http2" + } + } + }, + "frontend": { + "type": "project.v1", + "path": "..\\MyFrontend\\MyFrontend.csproj", + "env": { + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true", + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true", + "services__basketservice__0": "{basketservice.bindings.http.url}", + "services__basketservice__1": "{basketservice.bindings.https.url}", + "services__catalogservice__0": "{catalogservice.bindings.http.url}" + }, + "bindings": { + "http": { + "scheme": "http", + "protocol": "tcp", + "transport": "http" + }, + "https": { + "scheme": "https", + "protocol": "tcp", + "transport": "http" + } + } + }, + "orderprocessor": { + "type": "project.v1", + "path": "..\\OrderProcessor\\OrderProcessor.csproj", + "env": { + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true", + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true", + "ConnectionStrings__messaging": "{messaging.connectionString}" + }, + "bindings": { + "http": { + "scheme": "http", + "protocol": "tcp", + "transport": "http" + }, + "https": { + "scheme": "https", + "protocol": "tcp", + "transport": "http" + } + } + }, + "apigateway": { + "type": "project.v1", + "path": "..\\ApiGateway\\ApiGateway.csproj", + "env": { + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true", + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true", + "services__basketservice__0": "{basketservice.bindings.http.url}", + "services__basketservice__1": "{basketservice.bindings.https.url}", + "services__catalogservice__0": "{catalogservice.bindings.http.url}", + "services__catalogservice__1": "{catalogservice.bindings.https.url}" + }, + "bindings": { + "http": { + "scheme": "http", + "protocol": "tcp", + "transport": "http" + }, + "https": { + "scheme": "https", + "protocol": "tcp", + "transport": "http" + } + } + }, + "catalogdbapp": { + "type": "project.v1", + "path": "..\\CatalogDb\\CatalogDb.csproj", + "env": { + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true", + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true", + "ConnectionStrings__catalogdb": "{catalogdb.connectionString}" + }, + "bindings": { + "http": { + "scheme": "http", + "protocol": "tcp", + "transport": "http" + }, + "https": { + "scheme": "https", + "protocol": "tcp", + "transport": "http" + } + } + } + } +} \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/AppHost/Directory.Build.props dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/AppHost/Directory.Build.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/AppHost/Directory.Build.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/AppHost/Directory.Build.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/AppHost/Directory.Build.targets dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/AppHost/Directory.Build.targets --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/AppHost/Directory.Build.targets 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/AppHost/Directory.Build.targets 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/AppHost/Program.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/AppHost/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/AppHost/Program.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/AppHost/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,34 @@ +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddAzureProvisioning(); + +var catalogDb = builder.AddPostgresContainer("postgres").AddDatabase("catalogdb"); + +var basketCache = builder.AddRedisContainer("basketcache"); + +var catalogService = builder.AddProject("catalogservice") + .WithReference(catalogDb) + .WithReplicas(2); + +var messaging = builder.AddRabbitMQContainer("messaging"); + +var basketService = builder.AddProject("basketservice") + .WithReference(basketCache) + .WithReference(messaging); + +builder.AddProject("frontend") + .WithReference(basketService) + .WithReference(catalogService.GetEndpoint("http")); + +builder.AddProject("orderprocessor") + .WithReference(messaging) + .WithLaunchProfile("OrderProcessor"); + +builder.AddProject("apigateway") + .WithReference(basketService) + .WithReference(catalogService); + +builder.AddProject("catalogdbapp") + .WithReference(catalogDb); + +builder.Build().Run(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/AppHost/Properties/launchSettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/AppHost/Properties/launchSettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/AppHost/Properties/launchSettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/AppHost/Properties/launchSettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,39 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:15888", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16031" + } + }, + "generate-manifest": { + "commandName": "Project", + "launchBrowser": true, + "dotnetRunMessages": true, + "commandLineArgs": "--publisher manifest --output-path aspire-manifest.json", + "applicationUrl": "http://localhost:15888", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16031" + } + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:17888;http://localhost:15888", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:18031" + } + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json" +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/azure.yaml dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/azure.yaml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/azure.yaml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/azure.yaml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json + +name: Aspire +hooks: + preprovision: + continueOnError: false + interactive: false + run: dotnet run --project AppHost\AppHost.csproj -- --target azure --output-path .\infra\eshoplite + shell: pwsh diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/appsettings.Development.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/appsettings.Development.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/appsettings.Development.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/appsettings.Development.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/appsettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/appsettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,29 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Azure": "Warning" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + }, + "Aspire": { + "RabbitMQ": { + "Client": { + "OrderQueueName": "orders" + } + }, + "StackExchange": { + "Redis": { + "ConfigurationOptions": { + "ConnectRetry": 2 + } + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/BasketService.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/BasketService.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/BasketService.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/BasketService.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,143 @@ +using System.Text.Json; +using BasketService.Models; +using BasketService.Repositories; +using Grpc.Core; +using GrpcBasket; +using RabbitMQ.Client; +namespace BasketService; + +public class BasketService(IBasketRepository repository, IConfiguration configuration, IServiceProvider serviceProvider, ILogger logger) : Basket.BasketBase +{ + private readonly IBasketRepository _repository = repository; + private readonly IConfiguration _configuration = configuration; + private readonly ILogger _logger = logger; + private IConnection? _messageConnection; + + public override async Task GetBasketById(BasketRequest request, ServerCallContext context) + { + // Uncomment to force a delay for testing resiliency, etc. + //await Task.Delay(5000); + + var data = await _repository.GetBasketAsync(request.Id); + + if (data != null) + { + return MapToCustomerBasketResponse(data); + } + + return new CustomerBasketResponse(); + } + + public override async Task UpdateBasket(CustomerBasketRequest request, ServerCallContext context) + { + var customerBasket = MapToCustomerBasket(request); + var response = await _repository.UpdateBasketAsync(customerBasket); + + if (response is null) + { + throw new RpcException(new Status(StatusCode.NotFound, $"Basket with buyer id {request.BuyerId} does not exist")); + } + + return MapToCustomerBasketResponse(response); + } + + public override async Task CheckoutBasket(CheckoutCustomerBasketRequest request, ServerCallContext context) + { + var buyerId = request.BuyerId; + var basket = await _repository.GetBasketAsync(buyerId); + + if (basket is null) + { + throw new RpcException(new Status(StatusCode.NotFound, $"Basket with buyer id {request.BuyerId} does not exist")); + } + + var order = new Order() + { + Id = Guid.NewGuid().ToString(), + BuyerId = buyerId, + Items = basket.Items, + }; + + _logger.LogInformation("Checking out {Count} item(s) for BuyerId: {BuyerId}.", order.Items.Count, buyerId); + + _messageConnection ??= serviceProvider.GetService(); + if (_messageConnection is null) + { + _logger.LogWarning("RabbitMQ is unavailable. Ensure you have configured it in AppHosts's config / user secrets under 'ConnectionStrings:messaging'."); + } + else + { + const string configKeyName = "Aspire:RabbitMQ:Client:OrderQueueName"; + string? queueName = _configuration[configKeyName]; + if (string.IsNullOrEmpty(queueName)) + { + context.Status = new Status(StatusCode.Internal, $"Queue name not found. Please add a valid name for configuration key '{configKeyName}'."); + return new(); + } + + using var channel = _messageConnection.CreateModel(); + channel.QueueDeclare(queueName, exclusive: false); + channel.BasicPublish( + exchange: "", + routingKey: queueName, + basicProperties: null, + body: JsonSerializer.SerializeToUtf8Bytes(order)); + } + + await _repository.DeleteBasketAsync(buyerId); + + _logger.LogInformation("Order Id {Id} submitted.", order.Id); + + return new CheckoutCustomerBasketResponse(); + } + + public override async Task DeleteBasket(DeleteCustomerBasketRequest request, ServerCallContext context) + { + await _repository.DeleteBasketAsync(request.BuyerId); + return new DeleteCustomerBasketResponse(); + } + + private static CustomerBasketResponse MapToCustomerBasketResponse(CustomerBasket customerBasket) + { + var response = new CustomerBasketResponse + { + BuyerId = customerBasket.BuyerId + }; + + foreach (var item in customerBasket.Items) + { + response.Items.Add(new BasketItemResponse + { + Id = item.Id ?? Guid.NewGuid().ToString(), + OldUnitPrice = item.OldUnitPrice, + ProductId = item.ProductId, + Quantity = item.Quantity, + UnitPrice = item.UnitPrice + }); + } + + return response; + } + + private static CustomerBasket MapToCustomerBasket(CustomerBasketRequest customerBasketRequest) + { + var response = new CustomerBasket + { + BuyerId = customerBasketRequest.BuyerId + }; + + foreach (var item in customerBasketRequest.Items) + { + response.Items.Add(new BasketItem + { + Id = item.Id, + OldUnitPrice = item.OldUnitPrice, + ProductId = item.ProductId, + Quantity = item.Quantity, + UnitPrice = item.UnitPrice + }); + } + + return response; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/BasketService.csproj dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/BasketService.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/BasketService.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/BasketService.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,25 @@ + + + + net8.0 + f0611710-1eab-4ff6-a476-9610bb7a8416 + localhost:5001 + basket-service + mcr.microsoft.com/dotnet/aspnet:8.0-preview + + + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Models/BasketItem.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Models/BasketItem.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Models/BasketItem.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Models/BasketItem.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; + +namespace BasketService.Models; + +public class BasketItem : IValidatableObject +{ + public string? Id { get; set; } + public int ProductId { get; set; } + public decimal UnitPrice { get; set; } + public decimal OldUnitPrice { get; set; } + public int Quantity { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + var results = new List(); + + if (Quantity < 1) + { + results.Add(new ValidationResult("Invalid number of units", new[] { "Quantity" })); + } + + return results; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Models/CustomerBasket.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Models/CustomerBasket.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Models/CustomerBasket.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Models/CustomerBasket.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,20 @@ +namespace BasketService.Models; + +public class CustomerBasket +{ + public string? BuyerId { get; set; } + + public List Items { get; set; } = new(); + + public int TotalItemCount => Items.Sum(i => i.Quantity); + + public CustomerBasket() + { + } + + public CustomerBasket(string customerId) + { + BuyerId = customerId; + } +} + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Models/DecimalValue.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Models/DecimalValue.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Models/DecimalValue.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Models/DecimalValue.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,23 @@ +namespace GrpcBasket; + +public partial class DecimalValue +{ + private const decimal NanoFactor = 1_000_000_000; + public DecimalValue(long units, int nanos) + { + Units = units; + Nanos = nanos; + } + + public static implicit operator decimal(DecimalValue grpcDecimal) + { + return grpcDecimal.Units + grpcDecimal.Nanos / NanoFactor; + } + + public static implicit operator DecimalValue(decimal value) + { + var units = decimal.ToInt64(value); + var nanos = decimal.ToInt32((value - units) * NanoFactor); + return new DecimalValue(units, nanos); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Models/Order.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Models/Order.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Models/Order.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Models/Order.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +namespace BasketService.Models; + +public class Order +{ + public required string Id { get; set; } + public string? BuyerId { get; set; } + + public List Items { get; set; } = new(); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Program.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Program.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,18 @@ +using BasketService.Repositories; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); +builder.Services.AddGrpc(); + +builder.AddRedis("basketcache"); +builder.Services.AddTransient(); + +builder.AddRabbitMQ("messaging"); + +var app = builder.Build(); + +app.MapGrpcService(); +app.MapDefaultEndpoints(); + +app.Run(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Properties/launchSettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Properties/launchSettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Properties/launchSettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Properties/launchSettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,23 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5304", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:5305;http://localhost:5304", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Protos/basket.proto dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Protos/basket.proto --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Protos/basket.proto 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Protos/basket.proto 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,60 @@ +syntax = "proto3"; + +option csharp_namespace = "GrpcBasket"; + +package BasketApi; + +service Basket { + rpc GetBasketById(BasketRequest) returns (CustomerBasketResponse) {} + rpc UpdateBasket(CustomerBasketRequest) returns (CustomerBasketResponse) {} + rpc CheckoutBasket(CheckoutCustomerBasketRequest) returns (CheckoutCustomerBasketResponse) {} + rpc DeleteBasket(DeleteCustomerBasketRequest) returns (DeleteCustomerBasketResponse) {} +} + +message BasketRequest { + string id = 1; +} + +message CustomerBasketRequest { + string buyerId = 1; + repeated BasketItemResponse items = 2; +} + +message CustomerBasketResponse { + string buyerId = 1; + repeated BasketItemResponse items = 2; +} + +message BasketItemResponse { + string id = 1; + int32 productId = 2; + DecimalValue unitPrice = 4; + DecimalValue oldUnitPrice = 5; + int32 quantity = 6; +} + +message CheckoutCustomerBasketRequest { + string buyerId = 1; +} + +message CheckoutCustomerBasketResponse { +} + +message DeleteCustomerBasketRequest { + string buyerId = 1; +} + +message DeleteCustomerBasketResponse { +} + +// See: https://learn.microsoft.com/en-us/dotnet/architecture/grpc-for-wcf-developers/protobuf-data-types#decimals +// Example: 12345.6789 -> { units = 12345, nanos = 678900000 } +message DecimalValue { + + // Whole units part of the amount + int64 units = 1; + + // Nano units of the amount (10^-9) + // Must be same sign as units + sfixed32 nanos = 2; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Repositories/IBasketRepository.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Repositories/IBasketRepository.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Repositories/IBasketRepository.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Repositories/IBasketRepository.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,12 @@ +using BasketService.Models; + +namespace BasketService.Repositories; + +public interface IBasketRepository +{ + Task GetBasketAsync(string customerId); + IEnumerable GetUsers(); + Task UpdateBasketAsync(CustomerBasket basket); + Task DeleteBasketAsync(string id); +} + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Repositories/RedisBasketRepository.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Repositories/RedisBasketRepository.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/BasketService/Repositories/RedisBasketRepository.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/BasketService/Repositories/RedisBasketRepository.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,67 @@ +using System.Text.Json; +using BasketService.Models; +using StackExchange.Redis; + +namespace BasketService.Repositories; + +public class RedisBasketRepository(ILogger logger, IConnectionMultiplexer redis) : IBasketRepository +{ + private readonly ILogger _logger = logger; + private readonly IConnectionMultiplexer _redis = redis; + private readonly IDatabase _database = redis.GetDatabase(); + private static readonly JsonSerializerOptions s_jsonSerializerOptions = new() + { + PropertyNameCaseInsensitive = true, + }; + + public async Task DeleteBasketAsync(string id) + { + return await _database.KeyDeleteAsync(id); + } + + public IEnumerable GetUsers() + { + var server = GetServer(); + var data = server.Keys(); + + return data?.Select(k => k.ToString()) ?? Enumerable.Empty(); + } + + public async Task GetBasketAsync(string customerId) + { + var data = await _database.StringGetAsync(customerId); + + if (data.IsNullOrEmpty) + { + return null; + } + + return JsonSerializer.Deserialize(data!, s_jsonSerializerOptions); + } + + public async Task UpdateBasketAsync(CustomerBasket basket) + { + if (basket.BuyerId == null) + { + return null; + } + + var created = await _database.StringSetAsync(basket.BuyerId, JsonSerializer.Serialize(basket, s_jsonSerializerOptions)); + + if (!created) + { + _logger.LogInformation("Problem occur persisting the item."); + return null; + } + + _logger.LogInformation("Basket item persisted successfully."); + + return await GetBasketAsync(basket.BuyerId); + } + + private IServer GetServer() + { + var endpoint = _redis.GetEndPoints(); + return _redis.GetServer(endpoint.First()); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/build-containers.cmd dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/build-containers.cmd --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/build-containers.cmd 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/build-containers.cmd 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,15 @@ +pushd ApiGateway +dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer -p:PublishSingleFile=true --self-contained true +popd + +pushd CatalogService +dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer -p:PublishSingleFile=true --self-contained true +popd + +pushd BasketService +dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer -p:PublishSingleFile=true --self-contained true +popd + +pushd MyFrontend +dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer -p:PublishSingleFile=true --self-contained true +popd diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/appsettings.Development.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/appsettings.Development.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/appsettings.Development.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/appsettings.Development.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + // A connection string is here to enable use of the `dotnet ef` cmd line tool from the project root. + // If the configuration value is not present or not well-formed, the app will fail at startup. + // Note that some commands require the connection string to point to a real database in order to fully + // function (e.g. `dotnet ef database update`, `dotnet ef migrations list`). + "catalogdb": "Server=localhost;Port=5432;Database=NOT_A_REAL_DB" + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/appsettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/appsettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/CatalogDb.csproj dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/CatalogDb.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/CatalogDb.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/CatalogDb.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/CatalogDbInitializer.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/CatalogDbInitializer.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/CatalogDbInitializer.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/CatalogDbInitializer.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,116 @@ +using System.Diagnostics; +using Microsoft.EntityFrameworkCore; + +namespace CatalogDb; + +internal sealed class CatalogDbInitializer(IServiceProvider serviceProvider, ILogger logger) + : BackgroundService +{ + public const string ActivitySourceName = "Migrations"; + + private readonly ActivitySource _activitySource = new(ActivitySourceName); + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + using var scope = serviceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + await InitializeDatabaseAsync(dbContext, cancellationToken); + } + + private async Task InitializeDatabaseAsync(CatalogDbContext dbContext, CancellationToken cancellationToken) + { + var strategy = dbContext.Database.CreateExecutionStrategy(); + + using var activity = _activitySource.StartActivity("Migrating catalog database", ActivityKind.Client); + + var sw = Stopwatch.StartNew(); + + await strategy.ExecuteAsync(() => dbContext.Database.MigrateAsync(cancellationToken)); + + await SeedAsync(dbContext, cancellationToken); + + logger.LogInformation("Database initialization completed after {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds); + } + + private async Task SeedAsync(CatalogDbContext dbContext, CancellationToken cancellationToken) + { + logger.LogInformation("Seeding database"); + + static List GetPreconfiguredCatalogBrands() + { + return [ + new() { Brand = "Azure" }, + new() { Brand = ".NET" }, + new() { Brand = "Visual Studio" }, + new() { Brand = "SQL Server" }, + new() { Brand = "Other" } + ]; + } + + static List GetPreconfiguredCatalogTypes() + { + return [ + new() { Type = "Mug" }, + new() { Type = "T-Shirt" }, + new() { Type = "Sheet" }, + new() { Type = "USB Memory Stick" } + ]; + } + + static List GetPreconfiguredItems(DbSet catalogBrands, DbSet catalogTypes) + { + var dotNet = catalogBrands.First(b => b.Brand == ".NET"); + var other = catalogBrands.First(b => b.Brand == "Other"); + + var mug = catalogTypes.First(c => c.Type == "Mug"); + var tshirt = catalogTypes.First(c => c.Type == "T-Shirt"); + var sheet = catalogTypes.First(c => c.Type == "Sheet"); + + return [ + new() { CatalogType = tshirt, CatalogBrand = dotNet, AvailableStock = 100, Description = ".NET Bot Black Hoodie", Name = ".NET Bot Black Hoodie", Price = 19.5M, PictureFileName = "1.png" }, + new() { CatalogType = mug, CatalogBrand = dotNet, AvailableStock = 100, Description = ".NET Black & White Mug", Name = ".NET Black & White Mug", Price= 8.50M, PictureFileName = "2.png" }, + new() { CatalogType = tshirt, CatalogBrand = other, AvailableStock = 100, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureFileName = "3.png" }, + new() { CatalogType = tshirt, CatalogBrand = dotNet, AvailableStock = 100, Description = ".NET Foundation T-shirt", Name = ".NET Foundation T-shirt", Price = 12, PictureFileName = "4.png" }, + new() { CatalogType = sheet, CatalogBrand = other, AvailableStock = 100, Description = "Roslyn Red Sheet", Name = "Roslyn Red Sheet", Price = 8.5M, PictureFileName = "5.png" }, + new() { CatalogType = tshirt, CatalogBrand = dotNet, AvailableStock = 100, Description = ".NET Blue Hoodie", Name = ".NET Blue Hoodie", Price = 12, PictureFileName = "6.png" }, + new() { CatalogType = tshirt, CatalogBrand = other, AvailableStock = 100, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureFileName = "7.png" }, + new() { CatalogType = tshirt, CatalogBrand = other, AvailableStock = 100, Description = "Kudu Purple Hoodie", Name = "Kudu Purple Hoodie", Price = 8.5M, PictureFileName = "8.png" }, + new() { CatalogType = mug, CatalogBrand = other, AvailableStock = 100, Description = "Cup White Mug", Name = "Cup White Mug", Price = 12, PictureFileName = "9.png" }, + new() { CatalogType = sheet, CatalogBrand = dotNet, AvailableStock = 100, Description = ".NET Foundation Sheet", Name = ".NET Foundation Sheet", Price = 12, PictureFileName = "10.png" }, + new() { CatalogType = sheet, CatalogBrand = dotNet, AvailableStock = 100, Description = "Cup Sheet", Name = "Cup Sheet", Price = 8.5M, PictureFileName = "11.png" }, + new() { CatalogType = tshirt, CatalogBrand = other, AvailableStock = 100, Description = "Prism White TShirt", Name = "Prism White TShirt", Price = 12, PictureFileName = "12.png" } + ]; + } + + if (!dbContext.CatalogBrands.Any()) + { + var brands = GetPreconfiguredCatalogBrands(); + await dbContext.CatalogBrands.AddRangeAsync(brands, cancellationToken); + + logger.LogInformation("Seeding {CatalogBrandCount} catalog brands", brands.Count); + + await dbContext.SaveChangesAsync(cancellationToken); + } + + if (!dbContext.CatalogTypes.Any()) + { + var types = GetPreconfiguredCatalogTypes(); + await dbContext.CatalogTypes.AddRangeAsync(types, cancellationToken); + + logger.LogInformation("Seeding {CatalogTypeCount} catalog item types", types.Count); + + await dbContext.SaveChangesAsync(cancellationToken); + } + + if (!dbContext.CatalogItems.Any()) + { + var items = GetPreconfiguredItems(dbContext.CatalogBrands, dbContext.CatalogTypes); + await dbContext.CatalogItems.AddRangeAsync(items, cancellationToken); + + logger.LogInformation("Seeding {CatalogItemCount} catalog items", items.Count); + + await dbContext.SaveChangesAsync(cancellationToken); + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/CatalogDbInitializerHealthCheck.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/CatalogDbInitializerHealthCheck.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/CatalogDbInitializerHealthCheck.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/CatalogDbInitializerHealthCheck.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace CatalogDb; + +internal sealed class CatalogDbInitializerHealthCheck(CatalogDbInitializer dbInitializer) : IHealthCheck +{ + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + var task = dbInitializer.ExecuteTask; + + return task switch + { + { IsCompletedSuccessfully: true } => Task.FromResult(HealthCheckResult.Healthy()), + { IsFaulted: true } => Task.FromResult(HealthCheckResult.Unhealthy(task.Exception?.InnerException?.Message, task.Exception)), + { IsCanceled: true } => Task.FromResult(HealthCheckResult.Unhealthy("Database initialization was canceled")), + _ => Task.FromResult(HealthCheckResult.Degraded("Database initialization is still in progress")) + }; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/Migrations/20230517065317_Initial.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/Migrations/20230517065317_Initial.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/Migrations/20230517065317_Initial.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/Migrations/20230517065317_Initial.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,114 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CatalogDb.Migrations; + +/// +public partial class Initial : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "CatalogBrand", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false), + Brand = table.Column(type: "character varying(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrand", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogType", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false), + Type = table.Column(type: "character varying(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogType", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false), + Name = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "text", nullable: true), + Price = table.Column(type: "numeric", nullable: false), + PictureFileName = table.Column(type: "text", nullable: true), + CatalogTypeId = table.Column(type: "integer", nullable: false), + CatalogBrandId = table.Column(type: "integer", nullable: false), + AvailableStock = table.Column(type: "integer", nullable: false), + RestockThreshold = table.Column(type: "integer", nullable: false), + MaxStockThreshold = table.Column(type: "integer", nullable: false), + OnReorder = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrand_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrand", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogType_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogType", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "CatalogBrand"); + + migrationBuilder.DropTable( + name: "CatalogType"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/Migrations/20230517065317_Initial.Designer.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/Migrations/20230517065317_Initial.Designer.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/Migrations/20230517065317_Initial.Designer.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/Migrations/20230517065317_Initial.Designer.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,142 @@ +// +using CatalogDb; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace CatalogDb.Migrations +{ + [DbContext(typeof(CatalogDbContext))] + [Migration("20230517065317_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0-preview.3.23174.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("CatalogDb.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseHiLo(b.Property("Id"), "catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrand", (string)null); + }); + + modelBuilder.Entity("CatalogDb.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseHiLo(b.Property("Id"), "catalog_hilo"); + + b.Property("AvailableStock") + .HasColumnType("integer"); + + b.Property("CatalogBrandId") + .HasColumnType("integer"); + + b.Property("CatalogTypeId") + .HasColumnType("integer"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("MaxStockThreshold") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OnReorder") + .HasColumnType("boolean"); + + b.Property("PictureFileName") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("RestockThreshold") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog", (string)null); + }); + + modelBuilder.Entity("CatalogDb.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseHiLo(b.Property("Id"), "catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogType", (string)null); + }); + + modelBuilder.Entity("CatalogDb.CatalogItem", b => + { + b.HasOne("CatalogDb.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CatalogDb.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); +#pragma warning restore 612, 618 + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/Migrations/CatalogDbContextModelSnapshot.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/Migrations/CatalogDbContextModelSnapshot.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/Migrations/CatalogDbContextModelSnapshot.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/Migrations/CatalogDbContextModelSnapshot.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,139 @@ +// +using CatalogDb; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace CatalogDb.Migrations +{ + [DbContext(typeof(CatalogDbContext))] + partial class CatalogDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0-preview.3.23174.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("CatalogDb.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseHiLo(b.Property("Id"), "catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrand", (string)null); + }); + + modelBuilder.Entity("CatalogDb.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseHiLo(b.Property("Id"), "catalog_hilo"); + + b.Property("AvailableStock") + .HasColumnType("integer"); + + b.Property("CatalogBrandId") + .HasColumnType("integer"); + + b.Property("CatalogTypeId") + .HasColumnType("integer"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("MaxStockThreshold") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OnReorder") + .HasColumnType("boolean"); + + b.Property("PictureFileName") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("RestockThreshold") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog", (string)null); + }); + + modelBuilder.Entity("CatalogDb.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseHiLo(b.Property("Id"), "catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogType", (string)null); + }); + + modelBuilder.Entity("CatalogDb.CatalogItem", b => + { + b.HasOne("CatalogDb.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CatalogDb.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); +#pragma warning restore 612, 618 + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/Model.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/Model.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/Model.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/Model.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,139 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CatalogDb; + +public record Catalog(int FirstId, int NextId, bool IsLastPage, IEnumerable Data); + +public class CatalogDbContext(DbContextOptions options) : DbContext(options) +{ + // https://learn.microsoft.com/ef/core/performance/advanced-performance-topics#compiled-queries + + private static readonly Func> s_getCatalogItemsQuery = + EF.CompileAsyncQuery((CatalogDbContext context, int? catalogBrandId, int? before, int? after, int pageSize) => + context.CatalogItems.AsNoTracking() + .OrderBy(ci => ci.Id) + .Where(ci => catalogBrandId == null || ci.CatalogBrandId == catalogBrandId) + .Where(ci => before == null || ci.Id <= before) + .Where(ci => after == null || ci.Id >= after) + .Take(pageSize + 1)); + + public Task> GetCatalogItemsCompiledAsync(int? catalogBrandId, int? before, int? after, int pageSize) + { + return ToListAsync(s_getCatalogItemsQuery(this, catalogBrandId, before, after, pageSize)); + } + + public DbSet CatalogItems => Set(); + public DbSet CatalogBrands => Set(); + public DbSet CatalogTypes => Set(); + + protected override void OnModelCreating(ModelBuilder builder) + { + DefineCatalogBrand(builder.Entity()); + + DefineCatalogItem(builder.Entity()); + + DefineCatalogType(builder.Entity()); + } + + private static void DefineCatalogType(EntityTypeBuilder builder) + { + builder.ToTable("CatalogType"); + + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + + private static void DefineCatalogItem(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true); + + builder.Property(ci => ci.PictureFileName) + .IsRequired(false); + + builder.Ignore(ci => ci.PictureUri); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + + private static void DefineCatalogBrand(EntityTypeBuilder builder) + { + builder.ToTable("CatalogBrand"); + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + + private static async Task> ToListAsync(IAsyncEnumerable asyncEnumerable) + { + var results = new List(); + await foreach (var value in asyncEnumerable) + { + results.Add(value); + } + + return results; + } +} + +public class CatalogType +{ + public int Id { get; set; } + public required string Type { get; set; } +} + +public class CatalogBrand +{ + public int Id { get; set; } + public required string Brand { get; set; } +} + +public class CatalogItem +{ + public int Id { get; set; } + public required string Name { get; set; } + public string? Description { get; set; } + public decimal Price { get; set; } + public required string PictureFileName { get; set; } + public string? PictureUri { get; set; } + + public int CatalogTypeId { get; set; } + public required CatalogType CatalogType { get; set; } + + public int CatalogBrandId { get; set; } + public required CatalogBrand CatalogBrand { get; set; } + public int AvailableStock { get; set; } + public int RestockThreshold { get; set; } + public int MaxStockThreshold { get; set; } + public bool OnReorder { get; set; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/Program.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/Program.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,21 @@ +using CatalogDb; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); +builder.AddNpgsqlDbContext("catalogdb"); + +builder.Services.AddOpenTelemetry() + .WithTracing(tracing => tracing.AddSource(CatalogDbInitializer.ActivitySourceName)); + +builder.Services.AddSingleton(); +builder.Services.AddHostedService(sp => sp.GetRequiredService()); +builder.Services.AddHealthChecks() + .AddCheck("DbInitializer", null); + +var app = builder.Build(); + +app.MapDefaultEndpoints(); + +await app.RunAsync(); + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/Properties/launchSettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/Properties/launchSettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogDb/Properties/launchSettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogDb/Properties/launchSettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,40 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:21203", + "sslPort": 44392 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "readiness", + "applicationUrl": "http://localhost:5193", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "readiness", + "applicationUrl": "https://localhost:7163;http://localhost:5193", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/appsettings.Development.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/appsettings.Development.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/appsettings.Development.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/appsettings.Development.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/appsettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/appsettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/CatalogApi.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/CatalogApi.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/CatalogApi.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/CatalogApi.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,54 @@ +using CatalogDb; + +namespace CatalogService; + +public static class CatalogApi +{ + public static RouteGroupBuilder MapCatalogApi(this IEndpointRouteBuilder routes) + { + var group = routes.MapGroup("/api/v1/catalog"); + + group.WithTags("Catalog"); + + group.MapGet("items/type/all/brand/{catalogBrandId?}", async (int? catalogBrandId, CatalogDbContext catalogContext, int? before, int? after, int pageSize = 8) => + { + var itemsOnPage = await catalogContext.GetCatalogItemsCompiledAsync(catalogBrandId, before, after, pageSize); + + var (firstId, nextId) = itemsOnPage switch + { + [] => (0, 0), + [var only] => (only.Id, only.Id), + [var first, .., var last] => (first.Id, last.Id) + }; + + return new Catalog( + firstId, + nextId, + itemsOnPage.Count < pageSize, + itemsOnPage.Take(pageSize)); + }); + + group.MapGet("items/{catalogItemId:int}/image", async (int catalogItemId, CatalogDbContext catalogDbContext, IHostEnvironment environment) => + { + var item = await catalogDbContext.CatalogItems.FindAsync(catalogItemId); + + if (item is null) + { + return Results.NotFound(); + } + + var path = Path.Combine(environment.ContentRootPath, "Images", item.PictureFileName); + + if (!File.Exists(path)) + { + return Results.NotFound(); + } + + return Results.File(path, "image/jpeg"); + }) + .Produces(404) + .Produces(200, contentType: "image/jpeg"); + + return group; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/CatalogService.csproj dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/CatalogService.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/CatalogService.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/CatalogService.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,26 @@ + + + + net8.0 + 16965dbf-c1f8-4682-b7d8-e250ac520544 + preview + localhost:5001 + catalog-service + mcr.microsoft.com/dotnet/aspnet:8.0-preview + + + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/CatalogService.http dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/CatalogService.http --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/CatalogService.http 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/CatalogService.http 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,7 @@ +@MyBackend_HostAddress = http://localhost:5237 + +GET {{MyBackend_HostAddress}}/api/v1/catalog/items/type/all/brand +Accept: application/json + +### + Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Images/10.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Images/10.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Images/11.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Images/11.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Images/12.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Images/12.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Images/13.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Images/13.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Images/14.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Images/14.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Images/1.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Images/1.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Images/2.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Images/2.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Images/3.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Images/3.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Images/4.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Images/4.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Images/5.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Images/5.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Images/6.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Images/6.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Images/7.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Images/7.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Images/8.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Images/8.png differ Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Images/9.png and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Images/9.png differ diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Program.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Program.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,28 @@ +using CatalogDb; +using CatalogService; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); +builder.AddNpgsqlDbContext("catalogdb"); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddProblemDetails(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} +else +{ + app.UseExceptionHandler(); +} + +app.MapCatalogApi(); +app.MapDefaultEndpoints(); + +app.Run(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Properties/launchSettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Properties/launchSettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/CatalogService/Properties/launchSettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/CatalogService/Properties/launchSettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,25 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5237", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:5239;http://localhost:5237", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/deploy.cmd dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/deploy.cmd --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/deploy.cmd 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/deploy.cmd 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,6 @@ +call ./build-containers.cmd + +pushd Deployment +call ./undeploy.cmd +call ./deploy.cmd +popd diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/api-gateway.yml dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/api-gateway.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/api-gateway.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/api-gateway.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,43 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: api-gateway + labels: + app: api-gateway +spec: + selector: + matchLabels: + app: api-gateway + replicas: 1 + template: + metadata: + labels: + app: api-gateway + spec: + containers: + - name: main + image: localhost:5001/api-gateway + imagePullPolicy: Always + ports: + - containerPort: 8080 + terminationGracePeriodSeconds: 180 + minReadySeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 +--- +apiVersion: v1 +kind: Service +metadata: + name: api-gateway +spec: + type: LoadBalancer + selector: + app: api-gateway + ports: + - name: http + port: 8888 + targetPort: 8080 + #- name: https + # port: 433 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/basket-service.yml dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/basket-service.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/basket-service.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/basket-service.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,95 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: basket-service + labels: + name: basket-service +spec: + selector: + matchLabels: + name: basket-service + replicas: 3 + template: + metadata: + labels: + name: basket-service + spec: + containers: + - name: main + image: localhost:5001/basket-service + imagePullPolicy: Always + ports: + - containerPort: 8080 + - containerPort: 8443 + env: + - name: ConnectionStrings__basketCache + value: "redis" + - name: ASPNETCORE_URLS + value: "https://+:8443;http://+:8080" + - name: ASPNETCORE_Kestrel__Certificates__Default__Path + value: "/var/run/secrets/certs/tls.crt" + - name: ASPNETCORE_Kestrel__Certificates__Default__KeyPath + value: "/var/run/secrets/certs/tls.key" + volumeMounts: + - name: tls + mountPath: "/var/run/secrets/certs" + readOnly: true + volumes: + - name: tls + secret: + secretName: basket-tls + terminationGracePeriodSeconds: 180 + minReadySeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 +--- +apiVersion: v1 +kind: Service +metadata: + name: basket +spec: + selector: + name: basket-service + clusterIP: None + ports: + - name: http + port: 8080 + - name: https + port: 8443 +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: basket-tls + namespace: default +spec: + # Secret names are always required. + secretName: basket-tls + + duration: 2160h # 90d + renewBefore: 360h # 15d + subject: + organizations: + - eShop LLC + isCA: false + privateKey: + algorithm: RSA + encoding: PKCS1 + size: 2048 + usages: + - server auth + - client auth + # At least one of a DNS Name, URI, or IP address is required. + dnsNames: + - basket + # Issuer references are always required. + issuerRef: + name: ca-issuer + # We can reference ClusterIssuers by changing the kind here. + # The default value is Issuer (i.e. a locally namespaced Issuer) + kind: Issuer + # This is optional since cert-manager will default to this value however + # if you are using an external issuer, change this to that issuer group. + group: cert-manager.io diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/catalog-service.yml dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/catalog-service.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/catalog-service.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/catalog-service.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,46 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: catalog-service + labels: + name: catalog-service +spec: + selector: + matchLabels: + name: catalog-service + replicas: 3 + template: + metadata: + labels: + name: catalog-service + spec: + containers: + - name: main + image: localhost:5001/catalog-service + imagePullPolicy: Always + ports: + - containerPort: 8080 + #- containerPort: 443 + env: + - name: ConnectionStrings__catalog + value: "Host=postgres-service;Database=catalog;Username=postgres;Password=postgres" + terminationGracePeriodSeconds: 180 + minReadySeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 +--- +apiVersion: v1 +kind: Service +metadata: + name: catalog +spec: + selector: + name: catalog-service + clusterIP: None + ports: + - name: http + port: 8080 + #- name: https + # port: 433 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/cert-manager.yml dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/cert-manager.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/cert-manager.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/cert-manager.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Secret +type: kubernetes.io/tls +metadata: + name: ca-key-pair + namespace: default +data: # Generated via: openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=ca-issuer", converted to base64 via `base64 -w0 tls.key`, etc + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURDVENDQWZHZ0F3SUJBZ0lVWW9xc1pzY1htQjhSb0w1WWJCdVVidnRuRFBjd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0ZERVNNQkFHQTFVRUF3d0pZMkV0YVhOemRXVnlNQjRYRFRJek1EY3lPREU0TURrMU5Wb1hEVEkwTURjeQpOekU0TURrMU5Wb3dGREVTTUJBR0ExVUVBd3dKWTJFdGFYTnpkV1Z5TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGCkFBT0NBUThBTUlJQkNnS0NBUUVBcFU3THhLK3Foc0ZCTDNEdmtTTGNxc01VYjVlbVdmUE9UTmJUbWlCVnRPYUoKZmVqYThWbXQ5cjgvdXNxbzJlVWRRY2JwRUxwRjB5ZGFPOWhSUW1sdWQwdHRZaWVCVnU5WVE0K2Z6R1o0MVowWgpmU1NJY0ZwT1ZCVi9XRlZzd3doNmZMT0d5aGJOUG9pdkdVT2cxb1JRQ0Zod0gwdzI5aTMwYXhVNmRqalMxbW9nCkpqSk9YdkdodWN4RjErOTBDblBCRTdUQnl4QkJpN0hieUdiYmlWTjk1Y3ZmcHVTMjRPY29hMnZKUWo0dGhRN1kKZVc0Q2pocUVGbzVrdnM1K3V2Ymlja2NjcSsrRUZlSDFuTEY0V2w3dlZVekNqZVFmNkJiRWVQV2RQb3gyWVFWcApJL01iSzZzYkt4L3lieG5aR1pnZStXcXlJajc2QWRDUDhKeW53S3hlR1FJREFRQUJvMU13VVRBZEJnTlZIUTRFCkZnUVVSd3ptSUpKdU4wSWVZZ2Q0b09qSUEzZXVpZjB3SHdZRFZSMGpCQmd3Rm9BVVJ3em1JSkp1TjBJZVlnZDQKb09qSUEzZXVpZjB3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBQ0pUUAo5VUdMZml1NEVzKzVKbTdKajVnM1ErbEwvSjFsSkNRWDZ2TWZQWENTemtqTzU3RHA0NjRXMEhpdTdrZ1JOeDBYCjViUWVndjVJdjYxRDBSL2FLdEp3Y1hHVFRBZE5GT1Z3Uk9hNUJ2Q1RFUU1iY3o1eVBhMWNSeFYyTEErbCtMVWEKbGZyUU02OEZYRmtCWmxZSjdCdldwSkszTGRBa0IwQmM5RmpCT01iQTZCWlNEZkZtZHRuc1Q1VTFyVEdUV3dqdwppbXBqNXpYVVQrYWRvU2c0b29SOXIveGkzYjBCeC9rQzFQU1p4R2V5clFTNDNqdEljckZDOTFZU2N1S29MZDRsClJ3bzdjSWZaSFN1MDNMZm1NR2x0Nk1kNkxib1A3SDZVZGJCUjIrdUF4eExrWlNRN3F1djljMzNaVGVWQ2lhL0sKSTFtclpmd3V5K1FxK1JqdFNRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRQ2xUc3ZFcjZxR3dVRXYKY08rUkl0eXF3eFJ2bDZaWjg4NU0xdE9hSUZXMDVvbDk2TnJ4V2EzMnZ6KzZ5cWpaNVIxQnh1a1F1a1hUSjFvNwoyRkZDYVc1M1MyMWlKNEZXNzFoRGo1L01abmpWblJsOUpJaHdXazVVRlg5WVZXekRDSHA4czRiS0ZzMCtpSzhaClE2RFdoRkFJV0hBZlREYjJMZlJyRlRwMk9OTFdhaUFtTWs1ZThhRzV6RVhYNzNRS2M4RVR0TUhMRUVHTHNkdkkKWnR1SlUzM2x5OSttNUxiZzV5aHJhOGxDUGkyRkR0aDViZ0tPR29RV2ptUyt6bjY2OXVKeVJ4eXI3NFFWNGZXYwpzWGhhWHU5VlRNS041Qi9vRnNSNDlaMCtqSFpoQldrajh4c3JxeHNySC9Kdkdka1ptQjc1YXJJaVB2b0IwSS93Cm5LZkFyRjRaQWdNQkFBRUNnZ0VBQXRUYUR1aGE0QzdEenZGdEVHUWpyemhDOFBrM2RWamttVmZySjl1aXJZUHUKMzRyRjR2RXExb2JXcTJjd3FVc2dPelNTTmZ5dUk1UjJtbFNRS2haQlRsYTFoNDNwYkZ1bnZDclpQVkIvMmU4cApwUHFqbHlLOEx0QWFTUUV0UG5ITHNQZmRhTTFSN3BQN3NDVTM4UUN6R0dkL3VTWXVnVkk1YitoSHpYSUNUaXBHCnJLODhBTkxNbG1MQzlxRms2VUh0SFRnbDFGdnBBN0NreXZmWTd4MlBGNnpUTy84QTFWYTF2MDErY0E5UXdHV2EKT0hibWNXRXRpM0JJYlk3THN6RExOcnhMREdzS2hNcmJFM2MxSTc5OHhrTnJHVlgzSWpFWGxyMDNpTkxxQklSZApPdEFPNGgrckFYbjBzS0JpN3FEOUQ1WVNHZWxiSlZwMnk1M2tVWGpjNFFLQmdRRG5sSlZvQVh1RTg1VkEvODU2CmtYK2drdkpiNUEvY0k5Sm1oWjI2Rk9leFFLbllrMFRKWEZaSXpQcWR6R0hJL3Q3QytTbUkwSTFMTlR5R1QxS3UKZWNlT3A3SDBVaXR3czIwbzkzV1hRb2liamRyVDZwSUFaME9oNForc1NiYXEwdGxZSTZxblF0RksyVHdZeVdCZwpvS2pTN0NtMGhmTThrRVNoYkQzajR3T1A0UUtCZ1FDMnZUWUJOdm1PNjZ0UU0rTHZNY2ZuZm9ycHJZMmx1UHhxClVUbkJMVTdSVUhWTUpyZXhuUndlYXBjZnVPMVNPTkRhclNnMVg5NjJNcHpockkrQ2g0V2NTV3JDcGI2VUxaRWUKR2ZZWmFkNmdpYjJ5VERwQU95dFkxSmZ0ejdQY1doNEYxOWVpcXFwN1owVDBKandFb0lVcnRVVmxIUEFBY1pHcwoxbzlXSGcvMU9RS0JnQ1J5QTlsc2NrNElMN01kRHhmSEVqanBKMjlDd3lRNEZ3Sy9nSVVxaVJRaVF4aXNoS1RUCi9WaFR1aFZmZHZ3MDhFWHJCSmhkaWY4T0Q4cHJiVzI4Q0tYd3lsYlRIN2NLNWVFT29Oai9SWlNGakt6Uk84MEkKYzJKa3FjWnBpMi95NHZXMkYwTmZocVNxNzRSUEhQWFlFdm11NVJsZUExdkNzS3BlNkphV3daYkJBb0dCQUk2Ywp0NnJYQm9mOWNDTVZlME9GTENlRDRGRmgrcXVjNUpEc2R4QkZsYjlROVZuSzFZMHR1YVliTlhteVRyUGVWMUJ4CjFuRmhNbVFxdDdoL2ZUZzNINVRLenlDOVRUQkRKMy9BYndtQ1VCWWxxUCtlOGM3eTNtOWk2bmJUbytDU0MrQXYKaE1BVVJSd3RpZWlvZGlwK1E2L0t0aStsRjhDZzRUbHI4VTBhQlFZeEFvR0JBSUVLWjFmRm4yc0dkaDdValM5bgo1MFRDNG1CeHFQbGlPU1VWVVh2cXRic2xCYmhrd3FlNWt1aDJ6ZGNnWnZ3TnU5eE45VmgyVVBhWVFlNWxvbm9zCmIxa3oyUERDald6YkxUUzRaLzRDSXJzU3psYVNSNFI0M0RRb0Y4bS8rRlZkKzk1bGVMVW9nVXdZaVZ0MXc3SG0KYXJlalNraUdZbW9PaFh0Z0RHNEhpNUpnCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: ca-issuer + namespace: default +spec: + ca: + secretName: ca-key-pair \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/deploy.cmd dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/deploy.cmd --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/deploy.cmd 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/deploy.cmd 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,7 @@ +kubectl apply -f ./cert-manager.yml +kubectl apply -f ./redis.yml +kubectl apply -f ./postgres.yml +kubectl apply -f ./api-gateway.yml +kubectl apply -f ./catalog-service.yml +kubectl apply -f ./basket-service.yml +kubectl apply -f ./my-frontend.yml diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/my-frontend.yml dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/my-frontend.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/my-frontend.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/my-frontend.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,44 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-frontend + labels: + app: my-frontend +spec: + selector: + matchLabels: + app: my-frontend + replicas: 1 + template: + metadata: + labels: + app: my-frontend + spec: + containers: + - name: main + image: localhost:5001/my-frontend + imagePullPolicy: Always + ports: + - containerPort: 8080 + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Production" + terminationGracePeriodSeconds: 180 + minReadySeconds: 60 + strategy: + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + +--- +apiVersion: v1 +kind: Service +metadata: + name: my-frontend +spec: + type: LoadBalancer + selector: + app: my-frontend + ports: + - name: http + port: 8080 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/postgres.yml dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/postgres.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/postgres.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/postgres.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,51 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: postgres-configuration + labels: + app: postgres +data: + POSTGRES_DB: postgres + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: postgres-statefulset + labels: + app: postgres +spec: + serviceName: "postgres" + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: postgres:12 + envFrom: + - configMapRef: + name: postgres-configuration + ports: + - containerPort: 5432 + name: postgresdb +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres-service + labels: + app: postgres +spec: + type: ClusterIP + ports: + - port: 5432 + name: postgres + selector: + app: postgres \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/redis.yml dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/redis.yml --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/redis.yml 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/redis.yml 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,41 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: mcr.microsoft.com/oss/bitnami/redis:6.0.8 + env: + - name: ALLOW_EMPTY_PASSWORD + value: "yes" + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 250m + memory: 256Mi + ports: + - containerPort: 6379 + name: redis +--- +apiVersion: v1 +kind: Service +metadata: + name: redis +spec: + ports: + - port: 6379 + selector: + app: redis +--- \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/undeploy.cmd dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/undeploy.cmd --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/Deployment/undeploy.cmd 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/Deployment/undeploy.cmd 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,6 @@ +kubectl delete -f ./redis.yml +kubectl delete -f ./postgres.yml +kubectl delete -f ./api-gateway.yml +kubectl delete -f ./catalog-service.yml +kubectl delete -f ./basket-service.yml +kubectl delete -f ./my-frontend.yml diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/eShopLite.sln dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/eShopLite.sln --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/eShopLite.sln 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/eShopLite.sln 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.33716.462 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiGateway", "ApiGateway\ApiGateway.csproj", "{B97E0EFC-E8A8-44A2-A7D4-4BEB0BD07E79}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasketService", "BasketService\BasketService.csproj", "{22E22164-27AE-4BC5-9A85-C16F0F5E49F7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CatalogService", "CatalogService\CatalogService.csproj", "{F8F2A226-59D7-4023-A7BE-602B6B0366D2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceDefaults", "ServiceDefaults\ServiceDefaults.csproj", "{938E6618-3061-4908-82AB-A2463F6B7BE8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyFrontend", "MyFrontend\MyFrontend.csproj", "{A92E2CCE-5B2B-461E-80AA-1669FE2EEF2E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppHost", "AppHost\AppHost.csproj", "{96FDD139-88F1-46DC-8487-4D5D7F1E5A8D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrderProcessor", "OrderProcessor\OrderProcessor.csproj", "{7926680B-CE02-4AE8-AED2-E95678309289}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B97E0EFC-E8A8-44A2-A7D4-4BEB0BD07E79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B97E0EFC-E8A8-44A2-A7D4-4BEB0BD07E79}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B97E0EFC-E8A8-44A2-A7D4-4BEB0BD07E79}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B97E0EFC-E8A8-44A2-A7D4-4BEB0BD07E79}.Release|Any CPU.Build.0 = Release|Any CPU + {22E22164-27AE-4BC5-9A85-C16F0F5E49F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22E22164-27AE-4BC5-9A85-C16F0F5E49F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22E22164-27AE-4BC5-9A85-C16F0F5E49F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22E22164-27AE-4BC5-9A85-C16F0F5E49F7}.Release|Any CPU.Build.0 = Release|Any CPU + {F8F2A226-59D7-4023-A7BE-602B6B0366D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8F2A226-59D7-4023-A7BE-602B6B0366D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8F2A226-59D7-4023-A7BE-602B6B0366D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8F2A226-59D7-4023-A7BE-602B6B0366D2}.Release|Any CPU.Build.0 = Release|Any CPU + {938E6618-3061-4908-82AB-A2463F6B7BE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {938E6618-3061-4908-82AB-A2463F6B7BE8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {938E6618-3061-4908-82AB-A2463F6B7BE8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {938E6618-3061-4908-82AB-A2463F6B7BE8}.Release|Any CPU.Build.0 = Release|Any CPU + {A92E2CCE-5B2B-461E-80AA-1669FE2EEF2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A92E2CCE-5B2B-461E-80AA-1669FE2EEF2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A92E2CCE-5B2B-461E-80AA-1669FE2EEF2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A92E2CCE-5B2B-461E-80AA-1669FE2EEF2E}.Release|Any CPU.Build.0 = Release|Any CPU + {96FDD139-88F1-46DC-8487-4D5D7F1E5A8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96FDD139-88F1-46DC-8487-4D5D7F1E5A8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96FDD139-88F1-46DC-8487-4D5D7F1E5A8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96FDD139-88F1-46DC-8487-4D5D7F1E5A8D}.Release|Any CPU.Build.0 = Release|Any CPU + {7926680B-CE02-4AE8-AED2-E95678309289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7926680B-CE02-4AE8-AED2-E95678309289}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7926680B-CE02-4AE8-AED2-E95678309289}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7926680B-CE02-4AE8-AED2-E95678309289}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8990AED3-C6AA-45CF-B4C0-771D7DF7CB96} + EndGlobalSection +EndGlobal diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/infra/eshoplite/app.bicep dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/infra/eshoplite/app.bicep --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/infra/eshoplite/app.bicep 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/infra/eshoplite/app.bicep 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,410 @@ +param location string = resourceGroup().location +param acrPullRoleDefinitionName string = '7f951dda-4ed3-4680-a7ca-43fe172d538d' +param acrPushRoleDefinitionName string = '8311e382-0749-4cb8-b61a-304f252e45ec' +param contributorRoleDefinitionName string = 'b24988ac-6180-42a0-ab88-20f7382dd24c' +@secure() +param postgresServerPassword string = newGuid() +resource storemydata2srv 'Microsoft.DBforPostgreSQL/flexibleServers@2021-06-01' = { + name: 'pgsql${uniqueString(resourceGroup().id, 'storemydata2')}' + location: location + sku: { + name: 'Standard_D4ds_v4' + tier: 'GeneralPurpose' + } + properties: { + version: '12' + administratorLogin: 'postgres' + administratorLoginPassword: postgresServerPassword + storage: { + storageSizeGB: 128 + } + } +} +resource cachymccacheface2 'Microsoft.Cache/redis@2022-06-01' = { + name: 'redis${uniqueString(resourceGroup().id, 'cachymccacheface2')}' + location: location + properties: { + sku: { + name: 'Basic' + family: 'C' + capacity: 0 + } + } +} +resource logWorkspace 'Microsoft.OperationalInsights/workspaces@2021-06-01' = { + name: 'logs${uniqueString(resourceGroup().id)}' + location: location + properties: { + sku: { + name: 'PerGB2018' + } + } +} +resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' = { + name: 'env${uniqueString(resourceGroup().id)}' + location: location + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logWorkspace.properties.customerId + sharedKey: logWorkspace.listKeys().primarySharedKey + } + } + } +} +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' = { + name: 'registry${uniqueString(resourceGroup().id)}' + location: location + sku: { + name: 'Basic' + } +} +resource acrPushIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: 'id${uniqueString(resourceGroup().id, 'push')}' + location: location +} +resource acrPullIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: 'id${uniqueString(resourceGroup().id, 'pull')}' + location: location +} +resource contributorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: contributorRoleDefinitionName + scope: subscription() +} +resource acrPullRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: acrPullRoleDefinitionName + scope: subscription() +} +resource contributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(acrPushIdentity.id, contributorRoleDefinitionName, containerRegistry.id) + properties: { + principalId: acrPushIdentity.properties.principalId + roleDefinitionId: contributorRoleDefinition.id + principalType: 'ServicePrincipal' + } + scope: containerRegistry +} +resource acrPullRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(acrPullIdentity.id, acrPullRoleDefinitionName, containerRegistry.id) + properties: { + principalId: acrPullIdentity.properties.principalId + roleDefinitionId: acrPullRoleDefinition.id + principalType: 'ServicePrincipal' + } + scope: containerRegistry +} +resource containerImageBootstrapScript0 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: 'import${uniqueString(resourceGroup().id, 'containerImageBootstrapScript0')}' + location: location + kind: 'AzureCLI' + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${acrPushIdentity.id}': { + } + } + } + properties: { + azCliVersion: '2.47.0' + retentionInterval: 'P1D' + scriptContent: 'az acr import --name ${containerRegistry.name} --source mcr.microsoft.com/azuredocs/containerapps-helloworld:latest --resource-group ${resourceGroup().name} --image catalog:latest' + } +} +resource containerApp0 'Microsoft.App/containerApps@2022-03-01' = { + name: 'catalog-${uniqueString(resourceGroup().id)}' + location: location + dependsOn: [ +containerImageBootstrapScript0 ] + identity: { + type: 'SystemAssigned,UserAssigned' + userAssignedIdentities: { + '${acrPullIdentity.id}': { + } + } + } + properties: { + managedEnvironmentId: containerAppEnvironment.id + configuration: { + ingress: { + external: true + targetPort: 80 + } + registries: [ + { + identity: acrPullIdentity.id + server: containerRegistry.properties.loginServer + } + ] + } + template: { + scale: { + minReplicas: 2 + } + containers: [ + { + name: 'container${uniqueString(resourceGroup().id)}' + image: '${containerRegistry.properties.loginServer}/catalog:latest' + env: [ + { + name: 'ConnectionStrings__Aspire.PostgreSQL' + value: 'Host=localhost;Database=catalog;Username=postgres;Password=postgres' + } + ] + resources: { + cpu: '0.25' + memory: '0.5Gi' + } + } + ] + } + } +} +resource containerImageBootstrapScript1 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: 'import${uniqueString(resourceGroup().id, 'containerImageBootstrapScript1')}' + location: location + kind: 'AzureCLI' + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${acrPushIdentity.id}': { + } + } + } + properties: { + azCliVersion: '2.47.0' + retentionInterval: 'P1D' + scriptContent: 'az acr import --name ${containerRegistry.name} --source mcr.microsoft.com/azuredocs/containerapps-helloworld:latest --resource-group ${resourceGroup().name} --image basket:latest' + } +} +resource containerApp1 'Microsoft.App/containerApps@2022-03-01' = { + name: 'basket-${uniqueString(resourceGroup().id)}' + location: location + dependsOn: [ +containerImageBootstrapScript1 ] + identity: { + type: 'SystemAssigned,UserAssigned' + userAssignedIdentities: { + '${acrPullIdentity.id}': { + } + } + } + properties: { + managedEnvironmentId: containerAppEnvironment.id + configuration: { + ingress: { + external: true + targetPort: 80 + } + registries: [ + { + identity: acrPullIdentity.id + server: containerRegistry.properties.loginServer + } + ] + } + template: { + scale: { + minReplicas: 2 + } + containers: [ + { + name: 'container${uniqueString(resourceGroup().id)}' + image: '${containerRegistry.properties.loginServer}/basket:latest' + env: [ + ] + resources: { + cpu: '0.25' + memory: '0.5Gi' + } + } + ] + } + } +} +resource containerImageBootstrapScript2 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: 'import${uniqueString(resourceGroup().id, 'containerImageBootstrapScript2')}' + location: location + kind: 'AzureCLI' + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${acrPushIdentity.id}': { + } + } + } + properties: { + azCliVersion: '2.47.0' + retentionInterval: 'P1D' + scriptContent: 'az acr import --name ${containerRegistry.name} --source mcr.microsoft.com/azuredocs/containerapps-helloworld:latest --resource-group ${resourceGroup().name} --image myfrontend:latest' + } +} +resource containerApp2 'Microsoft.App/containerApps@2022-03-01' = { + name: 'myfrontend-${uniqueString(resourceGroup().id)}' + location: location + dependsOn: [ +containerImageBootstrapScript2 ] + identity: { + type: 'SystemAssigned,UserAssigned' + userAssignedIdentities: { + '${acrPullIdentity.id}': { + } + } + } + properties: { + managedEnvironmentId: containerAppEnvironment.id + configuration: { + ingress: { + external: true + targetPort: 80 + } + registries: [ + { + identity: acrPullIdentity.id + server: containerRegistry.properties.loginServer + } + ] + } + template: { + scale: { + minReplicas: 2 + } + containers: [ + { + name: 'container${uniqueString(resourceGroup().id)}' + image: '${containerRegistry.properties.loginServer}/myfrontend:latest' + env: [ + ] + resources: { + cpu: '0.25' + memory: '0.5Gi' + } + } + ] + } + } +} +resource containerImageBootstrapScript3 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: 'import${uniqueString(resourceGroup().id, 'containerImageBootstrapScript3')}' + location: location + kind: 'AzureCLI' + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${acrPushIdentity.id}': { + } + } + } + properties: { + azCliVersion: '2.47.0' + retentionInterval: 'P1D' + scriptContent: 'az acr import --name ${containerRegistry.name} --source mcr.microsoft.com/azuredocs/containerapps-helloworld:latest --resource-group ${resourceGroup().name} --image orderprocessor:latest' + } +} +resource containerApp3 'Microsoft.App/containerApps@2022-03-01' = { + name: 'orderprocessor-${uniqueString(resourceGroup().id)}' + location: location + dependsOn: [ +containerImageBootstrapScript3 ] + identity: { + type: 'SystemAssigned,UserAssigned' + userAssignedIdentities: { + '${acrPullIdentity.id}': { + } + } + } + properties: { + managedEnvironmentId: containerAppEnvironment.id + configuration: { + ingress: { + external: true + targetPort: 80 + } + registries: [ + { + identity: acrPullIdentity.id + server: containerRegistry.properties.loginServer + } + ] + } + template: { + scale: { + minReplicas: 2 + } + containers: [ + { + name: 'container${uniqueString(resourceGroup().id)}' + image: '${containerRegistry.properties.loginServer}/orderprocessor:latest' + env: [ + ] + resources: { + cpu: '0.25' + memory: '0.5Gi' + } + } + ] + } + } +} +resource containerImageBootstrapScript4 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: 'import${uniqueString(resourceGroup().id, 'containerImageBootstrapScript4')}' + location: location + kind: 'AzureCLI' + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${acrPushIdentity.id}': { + } + } + } + properties: { + azCliVersion: '2.47.0' + retentionInterval: 'P1D' + scriptContent: 'az acr import --name ${containerRegistry.name} --source mcr.microsoft.com/azuredocs/containerapps-helloworld:latest --resource-group ${resourceGroup().name} --image apigateway:latest' + } +} +resource containerApp4 'Microsoft.App/containerApps@2022-03-01' = { + name: 'apigateway-${uniqueString(resourceGroup().id)}' + location: location + dependsOn: [ +containerImageBootstrapScript4 ] + identity: { + type: 'SystemAssigned,UserAssigned' + userAssignedIdentities: { + '${acrPullIdentity.id}': { + } + } + } + properties: { + managedEnvironmentId: containerAppEnvironment.id + configuration: { + ingress: { + external: true + targetPort: 80 + } + registries: [ + { + identity: acrPullIdentity.id + server: containerRegistry.properties.loginServer + } + ] + } + template: { + scale: { + minReplicas: 2 + } + containers: [ + { + name: 'container${uniqueString(resourceGroup().id)}' + image: '${containerRegistry.properties.loginServer}/apigateway:latest' + env: [ + ] + resources: { + cpu: '0.25' + memory: '0.5Gi' + } + } + ] + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/infra/main.bicep dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/infra/main.bicep --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/infra/main.bicep 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/infra/main.bicep 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,31 @@ +targetScope = 'subscription' + +@minLength(1) +@maxLength(64) +@description('Name of the environment that can be used as part of naming resource convention') +param environmentName string + +@minLength(1) +@description('Primary location for all resources') +param location string + +// Tags that should be applied to all resources. +// +// Note that 'azd-service-name' tags should be applied separately to service host resources. +// Example usage: +// tags: union(tags, { 'azd-service-name': }) +var tags = { + 'azd-env-name': environmentName +} + +resource rg 'Microsoft.Resources/resourceGroups@2022-09-01' = { + name: 'rg-${environmentName}' + location: location + tags: tags +} + +// Manually added! +module app 'myapp/app.bicep' = { + name: 'app-${environmentName}' + scope: rg +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/infra/main.parameters.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/infra/main.parameters.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/infra/main.parameters.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/infra/main.parameters.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,12 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environmentName": { + "value": "${AZURE_ENV_NAME}" + }, + "location": { + "value": "${AZURE_LOCATION}" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/appsettings.Development.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/appsettings.Development.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/appsettings.Development.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/appsettings.Development.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/appsettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/appsettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/AddToCart.razor dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/AddToCart.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/AddToCart.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/AddToCart.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,26 @@ +@inject BasketServiceClient BasketClient +@inject NavigationManager Navigation + + + + + + +@code { + [Parameter] + [EditorRequired] + public required CatalogItem Item { get; set; } + + [SupplyParameterFromForm] + public int ItemId { get; set; } + + private async Task HandleAddToCart() + { + await BasketClient.AddToCartAsync("user", ItemId); + + // Preserve query string + Navigation.NavigateTo($"/{new Uri(Navigation.Uri).Query}"); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/App.razor dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/App.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/App.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/App.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Cart.razor dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Cart.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Cart.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Cart.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,46 @@ +@inject BasketServiceClient BasketClient +@inject NavigationManager Navigation + +
    + @if (basketIsAvailable) + { + + + + } +
    + +@code { + bool basketIsAvailable; + int itemsInCart = 0; + + [Parameter] + public EventCallback BasketAvailabilityChanged { get; set; } + + protected override async Task OnInitializedAsync() + { + var (basket, isAvailable) = await BasketClient.GetBasketAsync("user"); + + if (basket is not null) + { + itemsInCart = basket.TotalItemCount; + } + basketIsAvailable = isAvailable; + await BasketAvailabilityChanged.InvokeAsync(basketIsAvailable); + } + + private async Task HandleCheckout() + { + await BasketClient.CheckoutBasketAsync("user"); + + // Preserve query string + Navigation.NavigateTo($"/{new Uri(Navigation.Uri).Query}"); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/_Imports.razor dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/_Imports.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/_Imports.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/_Imports.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,13 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using MyFrontend +@using MyFrontend.Components +@using MyFrontend.Components.Layout +@using MyFrontend.Services +@using BasketService.Models diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Layout/AppFooter.razor dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Layout/AppFooter.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Layout/AppFooter.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Layout/AppFooter.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,28 @@ +@using System.Runtime.InteropServices + + + +@code { + string Message = $"Powered by {RuntimeInformation.FrameworkDescription}"; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Layout/MainLayout.razor dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Layout/MainLayout.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Layout/MainLayout.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Layout/MainLayout.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +@inherits LayoutComponentBase + +@Body + +
    + An unhandled error has occurred. + Reload + 🗙 +
    diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Layout/MainLayout.razor.css dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Layout/MainLayout.razor.css --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Layout/MainLayout.razor.css 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Layout/MainLayout.razor.css 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,100 @@ +.page { + position: relative; + display: flex; + flex-direction: column; +} + +main { + flex: 1; +} + +.sidebar { + background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +} + +.top-row { + background-color: #f7f7f7; + border-bottom: 1px solid #d6d5d5; + justify-content: flex-end; + height: 3.5rem; + display: flex; + align-items: center; +} + + .top-row ::deep a, .top-row ::deep .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + text-decoration: none; + } + + .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { + text-decoration: underline; + } + + .top-row ::deep a:first-child { + overflow: hidden; + text-overflow: ellipsis; + } + +@media (max-width: 640.98px) { + .top-row:not(.auth) { + display: none; + } + + .top-row.auth { + justify-content: space-between; + } + + .top-row ::deep a, .top-row ::deep .btn-link { + margin-left: 0; + } +} + +@media (min-width: 641px) { + .page { + flex-direction: row; + } + + .sidebar { + width: 250px; + height: 100vh; + position: sticky; + top: 0; + } + + .top-row { + position: sticky; + top: 0; + z-index: 1; + } + + .top-row.auth ::deep a:first-child { + flex: 1; + text-align: right; + width: 0; + } + + .top-row, article { + padding-left: 2rem !important; + padding-right: 1.5rem !important; + } +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/NavigationManagerExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/NavigationManagerExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/NavigationManagerExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/NavigationManagerExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,19 @@ +namespace Microsoft.AspNetCore.Components; + +public static class NavigationManagerExtensions +{ + public static string ToAbsolutePath(this NavigationManager navigationManager, string uri) + { + // Workaround for Blazor issue: https://github.com/dotnet/aspnetcore/issues/51380 + + if (uri.StartsWith(navigationManager.BaseUri, StringComparison.Ordinal)) + { + // The absolute URI must be of the form "{baseUri}something" (where baseUri ends with a slash), + // and from that we return "something". If baseUri includes a path, we return that path too. + return new Uri(uri).PathAndQuery; + } + + var message = $"The URI '{uri}' is not contained by the base URI '{navigationManager.BaseUri}'."; + throw new ArgumentException(message); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Pages/Error.razor dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Pages/Error.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Pages/Error.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Pages/Error.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,28 @@ +@page "/Error" + +@using System.Diagnostics + +Error + +

    Error.

    +

    An error occurred while processing your request.

    + +@if (showRequestId) +{ +

    + Request ID: @requestId +

    +} + +@code { + private string? requestId; + private bool showRequestId => !string.IsNullOrEmpty(requestId); + + [CascadingParameter] + public HttpContext? HttpContext { get; set; } + + protected override void OnInitialized() + { + requestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Pages/Home.razor dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Pages/Home.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Pages/Home.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Pages/Home.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,94 @@ +@page "/" +@attribute [StreamRendering(true)] + +@inject CatalogServiceClient CatalogService + +eShopLite Product Catalog + +
    +

    <eShopLite />

    +
    +

    Product Catalog

    +
    + +
    + +
    + @if (catalog is { Data: var data }) + { +
    + @foreach (var item in data) + { +
    +
    + @item.Name +
    + +
    +
    +

    @item.Name

    +

    @item.Description

    +
    +

    @item.Price.ToString("C")

    + @if (basketIsAvailable) + { + + } +
    +
    +
    +
    + } +
    + + + } + else + { +

    Loading product catalog…

    + } +
    + + + +@code { + bool basketIsAvailable; + Catalog? catalog; + PaginationInfo paginationInfo = new(FirstId: 0, NextId: 0, HasPreviousPage: false, HasNextPage: false); + + [SupplyParameterFromQuery] + public int? Before { get; set; } + + [SupplyParameterFromQuery] + public int? After { get; set; } + + protected override async Task OnInitializedAsync() + { + catalog = await CatalogService.GetItemsAsync(Before, After); + + if (catalog is null) + { + return; + } + + paginationInfo = new PaginationInfo(catalog.FirstId, catalog.NextId, catalog.FirstId > 1, !catalog.IsLastPage); + } + + private void HandleBasketAvailabilityChanged(bool isAvailable) + { + basketIsAvailable = isAvailable; + } + + record PaginationInfo(int FirstId, int NextId, bool HasPreviousPage, bool HasNextPage); +} + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Pages/Home.razor.css dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Pages/Home.razor.css --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Pages/Home.razor.css 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Pages/Home.razor.css 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,7 @@ +.catalog-loading { + min-height: 500px; +} + +.catalog-item-image { + min-height: 295px; +} \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Routes.razor dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Routes.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Components/Routes.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Components/Routes.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,6 @@ + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/MyFrontend.csproj dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/MyFrontend.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/MyFrontend.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/MyFrontend.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,48 @@ + + + + net8.0 + localhost:5001 + my-frontend + mcr.microsoft.com/dotnet/aspnet:8.0-preview + + $(WarningsNotAsErrors);RZ10012;RZ10021 + + $(NoWarn);RZ10021 + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Program.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Program.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,35 @@ +using GrpcBasket; +using MyFrontend.Components; +using MyFrontend.Services; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +builder.Services.AddHttpForwarderWithServiceDiscovery(); + +builder.Services.AddHttpClient(c => c.BaseAddress = new("http://catalogservice")); + +builder.Services.AddSingleton() + .AddGrpcClient(o => o.Address = new("http://basketservice")); + +builder.Services.AddRazorComponents(); + +var app = builder.Build(); + +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/error", createScopeForErrors: true); +} + +app.UseStaticFiles(); + +app.UseAntiforgery(); + +app.MapRazorComponents(); + +app.MapForwarder("/catalog/images/{id}", "http://catalogservice", "/api/v1/catalog/items/{id}/image"); + +app.MapDefaultEndpoints(); + +app.Run(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Properties/launchSettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Properties/launchSettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Properties/launchSettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Properties/launchSettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,23 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5266", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7269;http://localhost:5266", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Services/BasketServiceClient.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Services/BasketServiceClient.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Services/BasketServiceClient.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Services/BasketServiceClient.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,111 @@ +using BasketService.Models; +using Grpc.Core; +using GrpcBasket; +using Polly.Timeout; + +namespace MyFrontend.Services; + +public class BasketServiceClient(Basket.BasketClient client) +{ + public async Task<(CustomerBasket? Basket, bool IsAvailable)> GetBasketAsync(string buyerId) + { + try + { + var response = await client.GetBasketByIdAsync(new BasketRequest { Id = buyerId }); + var result = !string.IsNullOrEmpty(response.BuyerId) ? MapToCustomerBasket(response) : null; + return (result, true); + } + catch (RpcException ex) when ( + // Service name could not be resolved + ex.StatusCode is StatusCode.Unavailable || + // Polly resilience timed out after retries + (ex.StatusCode is StatusCode.Internal && ex.Status.DebugException is TimeoutRejectedException)) + { + return (null, false); + } + } + + public async Task AddToCartAsync(string buyerId, int productId) + { + var (basket, _) = await GetBasketAsync(buyerId); + basket ??= new CustomerBasket(buyerId); + var found = false; + foreach (var item in basket.Items) + { + if (item.ProductId == productId) + { + ++item.Quantity; + found = true; + break; + } + } + + if (!found) + { + basket.Items.Add(new BasketItem + { + Id = Guid.NewGuid().ToString("N"), + ProductId = productId, + Quantity = 1 + }); + } + + var response = await client.UpdateBasketAsync(MapToCustomerBasketRequest(basket)); + var result = MapToCustomerBasket(response); + return result; + } + + public async Task CheckoutBasketAsync(string buyerId) + { + _ = await client.CheckoutBasketAsync(new CheckoutCustomerBasketRequest { BuyerId = buyerId }); + } + + public async Task DeleteBasketAsync(string buyerId) + { + _ = await client.DeleteBasketAsync(new DeleteCustomerBasketRequest { BuyerId = buyerId }); + } + + private static CustomerBasketRequest MapToCustomerBasketRequest(CustomerBasket customerBasket) + { + var response = new CustomerBasketRequest + { + BuyerId = customerBasket.BuyerId + }; + + foreach (var item in customerBasket.Items) + { + response.Items.Add(new BasketItemResponse + { + Id = item.Id, + OldUnitPrice = item.OldUnitPrice, + ProductId = item.ProductId, + Quantity = item.Quantity, + UnitPrice = item.UnitPrice + }); + } + + return response; + } + + private static CustomerBasket MapToCustomerBasket(CustomerBasketResponse wireBasket) + { + var response = new CustomerBasket + { + BuyerId = wireBasket.BuyerId + }; + + foreach (var item in wireBasket.Items) + { + response.Items.Add(new BasketItem + { + Id = item.Id, + OldUnitPrice = item.OldUnitPrice, + ProductId = item.ProductId, + Quantity = item.Quantity, + UnitPrice = item.UnitPrice + }); + } + + return response; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Services/CatalogServiceClient.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Services/CatalogServiceClient.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/Services/CatalogServiceClient.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/Services/CatalogServiceClient.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,35 @@ +using System.Globalization; + +namespace MyFrontend.Services; + +public class CatalogServiceClient(HttpClient client) +{ + public Task GetItemsAsync(int? before = null, int? after = null) + { + // Make the query string with encoded parameters + var query = (before, after) switch + { + (null, null) => default, + (int b, null) => QueryString.Create("before", b.ToString(CultureInfo.InvariantCulture)), + (null, int a) => QueryString.Create("after", a.ToString(CultureInfo.InvariantCulture)), + _ => throw new InvalidOperationException(), + }; + + return client.GetFromJsonAsync($"api/v1/catalog/items/type/all/brand{query}"); + } +} + +public record Catalog(int FirstId, int NextId, bool IsLastPage, IEnumerable Data); + +public record CatalogItem +{ + public int Id { get; init; } + public required string Name { get; init; } + public required string Description { get; init; } + public decimal Price { get; init; } + public string? PictureUri { get; init; } + public int CatalogBrandId { get; init; } + public required string CatalogBrand { get; init; } + public int CatalogTypeId { get; init; } + public required string CatalogType { get; init; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/wwwroot/site.css dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/wwwroot/site.css --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/MyFrontend/wwwroot/site.css 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/MyFrontend/wwwroot/site.css 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,291 @@ +:root { + --font-family: system-ui; + --border-color: #ccc; + --border-color-hover: #aaa; + --text-color: #555; + --text-heading-color: #222; + --quick-quarter-second-transition: all ease-in-out .25s; + --smooth-one-second-transition: all ease-in-out 1s; + --dotnet-purple-color: #512bd4; + --dotnet-purple-color-hover: #512bd4; +} + +html { + background: var(--dotnet-purple-color); + background: linear-gradient(0deg, rgba(81,43,212,0.15) 0%, rgba(255,255,255,1) 100%); +} + +* { + font-family: var(--font-family); + user-select: none; +} + +.d-flex { + display: flex; +} + +.flex-spacer { + flex-grow: 1; +} + +.pa-4 { + padding: 1.2rem; +} + +.animated-underline { + color: var(--text-color); + text-decoration: none; + transition: var(--quick-quarter-second-transition); +} + + .animated-underline:hover { + text-shadow: 0 0 1px black; + filter: brightness(50%); + } + + .animated-underline::after { + content: ""; + display: block; + width: 100%; + height: 1px; + background-color: var(--text-color); + transition: transform 0.2s ease-in-out; + transform: scale(0); + } + + .animated-underline:hover::after { + transform: scale(1); + } + +.justify-space-evenly { + justify-content: space-evenly; +} + +.justify-space-around { + justify-content: space-around; +} + +.justify-content-center { + justify-content: center; +} + +.justify-content-end { + justify-content: flex-end; +} + +.align-items-center { + align-items: center; +} + +.align-content-end { + align-content: flex-end; +} + +.text-align-center { + text-align: center; +} + +.pointer-events-none { + pointer-events: none; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 1rem; +} + +.grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + grid-gap: 1rem; + transition: var(--smooth-one-second-transition); +} + +.grid-item { + border: 2px solid var(--border-color); + border-radius: 6px; + box-shadow: var(--border-color) 0px 0px 5px; + background-color: whitesmoke; +} + + .grid-item:hover { + box-shadow: var(--border-color) 0px 0px 10px; + border-color: var(--border-color-hover); + } + +.grid-item-content { + display: flex; + flex-direction: column; +} + + .grid-item-content img { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } + + .grid-item-content:hover > img { + cursor: pointer; + filter: brightness(90%); + overflow: hidden; + transition: all ease-in-out .25s; + } + + .grid-item-content:hover .quick-view-overlay { + filter: brightness(120%); + } + +.quick-view-overlay { + position: absolute; + pointer-events: none; + cursor: pointer; + color: whitesmoke; + font-size: 1.5rem; + padding: .5rem; + margin-right: 1rem; +} + +.text { + color: var(--text-color); + text-decoration: none; +} + +.d-flex .text { + margin: .5rem 2rem; +} + +.grid-item-text { + text-align: center; + color: var(--text-color); +} + + .grid-item-text h4 { + color: var(--text-heading-color); + } + + .grid-item-text .item-price { + color: darkgreen; + font-weight: 700; + } + + .grid-item-text .item-description { + margin: .5rem; + } + +.pager { + display: flex; + justify-content: center; + align-items: center; + margin-top: 1rem; + font-size: 1.2rem; +} + + .pager .button { + text-decoration: none; + color: black; + } + + .pager .button[disable] { + pointer-events: none; + user-select: none; + color: #999; + } + + .pager .button:not([disable]):hover { + transition: all ease-in-out .5s; + background-color: var(--border-color); + } + + .pager .next { + padding: .5rem 1.5rem; + background-color: whitesmoke; + border-bottom-right-radius: 8px; + border-top-right-radius: 8px; + border: 2px solid var(--border-color); + border-left: none; + } + + .pager .next .fa { + padding-left: .5rem; + } + + .pager .previous { + padding: .5rem 1.5rem; + background-color: whitesmoke; + border-bottom-left-radius: 8px; + border-top-left-radius: 8px; + border: 2px solid var(--border-color); + border-right: none; + } + + .pager .previous .fa { + padding-right: .5rem; + } + +.cart-button:not(:disabled) { + font-size: 1.5rem; + cursor: pointer; + border: 2px solid transparent; + border-radius: 4rem; + padding: .4rem .8rem; + background-color: transparent; + transition: all ease-in-out .25s; +} + + .cart-button:not(:disabled):hover { + color: var(--dotnet-purple-color); + border-color: var(--border-color-hover); + background-color: #ccc; + } + +.fa.fa-stack-1x.badge { + color: white; + background-color: var(--dotnet-purple-color); + border-radius: 50%; + font-family: system-ui; + width: 1.5rem; + height: 1.5rem; + font-size: 1rem; + top: 15%; + right: 15%; + left: initial; + line-height: 1.5rem; + border: 2px solid white; +} + +.fa-shopping-cart.fa-stack-4x { + font-size: 4rem; +} + +.fa-stack.fa-lg.cart-stack { + line-height: 4rem; + width: 4rem; + height: 4rem; + transition: var(--quick-quarter-second-transition); +} + + .fa-stack.fa-lg.cart-stack:hover { + cursor: pointer; + filter: opacity(.7); + } + +.app-name { + font-weight: bolder; + font-size: 1.5rem; + font-family: monospace; + color: var(--dotnet-purple-color); +} + +.text-danger { + color: red; +} + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/OrderProcessor/appsettings.Development.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/OrderProcessor/appsettings.Development.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/OrderProcessor/appsettings.Development.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/OrderProcessor/appsettings.Development.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/OrderProcessor/appsettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/OrderProcessor/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/OrderProcessor/appsettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/OrderProcessor/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,17 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information", + "Azure": "Warning" + } + }, + + "Aspire": { + "RabbitMQ": { + "Client": { + "OrderQueueName": "orders" + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/OrderProcessor/OrderProcessingWorker.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/OrderProcessor/OrderProcessingWorker.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/OrderProcessor/OrderProcessingWorker.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/OrderProcessor/OrderProcessingWorker.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,76 @@ +using System.Diagnostics; +using System.Text.Json; +using BasketService.Models; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; + +namespace OrderProcessor; + +public class OrderProcessingWorker : BackgroundService +{ + private readonly ILogger _logger; + private readonly IConfiguration _config; + private readonly IServiceProvider _serviceProvider; + private IConnection? _messageConnection; + private IModel? _messageChannel; + + public OrderProcessingWorker(ILogger logger, IConfiguration config, IServiceProvider serviceProvider) + { + _logger = logger; + _config = config; + _serviceProvider = serviceProvider; + } + + protected override Task ExecuteAsync(CancellationToken stoppingToken) + { + return Task.Factory.StartNew(() => + { + const string configKeyName = "Aspire:RabbitMQ:Client:OrderQueueName"; + string queueName = _config[configKeyName] ?? "orders"; + + _messageConnection = _serviceProvider.GetRequiredService(); + + _messageChannel = _messageConnection.CreateModel(); + _messageChannel.QueueDeclare(queueName, exclusive: false); + + var consumer = new EventingBasicConsumer(_messageChannel); + consumer.Received += ProcessMessageAsync; + + _messageChannel.BasicConsume(queue: queueName, + autoAck: true, + consumer: consumer); + }, stoppingToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + } + + public override async Task StopAsync(CancellationToken cancellationToken) + { + await base.StopAsync(cancellationToken); + + _messageChannel?.Dispose(); + } + + private void ProcessMessageAsync(object? sender, BasicDeliverEventArgs args) + { + _logger.LogInformation($"Processing Order at: {DateTime.UtcNow}"); + + var message = args.Body; + + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug(""" + MessageId:{MessageId} + MessageBody:{Body} + """, args.BasicProperties.MessageId, message); + } + var order = JsonSerializer.Deserialize(message.Span) ?? new Order() { Id = "fake" }; + + Activity.Current?.AddTag("order-id", order.Id); + Activity.Current?.AddTag("product-count", order.Items.Count); + + _logger.LogInformation(""" + OrderId:{Id} + BuyerId:{BuyerId} + ProductCount:{Count} + """, order.Id, order.BuyerId, order.Items.Count); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/OrderProcessor/OrderProcessor.csproj dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/OrderProcessor/OrderProcessor.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/OrderProcessor/OrderProcessor.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/OrderProcessor/OrderProcessor.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,21 @@ + + + + net8.0 + dotnet-OrderProcessor-174b1029-6096-4b07-86b8-1b2d821d0830 + + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/OrderProcessor/Program.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/OrderProcessor/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/OrderProcessor/Program.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/OrderProcessor/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +using OrderProcessor; + +var builder = Host.CreateApplicationBuilder(args); + +builder.AddServiceDefaults(); + +builder.AddRabbitMQ("messaging"); +builder.Services.AddHostedService(); + +var host = builder.Build(); +host.Run(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/OrderProcessor/Properties/launchSettings.json dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/OrderProcessor/Properties/launchSettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/OrderProcessor/Properties/launchSettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/OrderProcessor/Properties/launchSettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,12 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "OrderProcessor": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/README.md dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/README.md --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/README.md 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/README.md 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,15 @@ +## Instructions for running in Kubernetes locally + +* Install Docker Desktop +* Enable Kubernetes support in Docker Desktop +* Start a local image repository + +``` +> docker run -d -p 5001:5000 --restart always --name registry registry:2 +``` + +* Build the containers, pushing them to the local repository, and deploy to kubernetes + +``` +> ./deploy.cmd +``` diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/ServiceDefaults/Extensions.cs dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/ServiceDefaults/Extensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/ServiceDefaults/Extensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/ServiceDefaults/Extensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,119 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Logs; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +public static class Extensions +{ + public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder) + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.UseServiceDiscovery(); + }); + + return builder; + } + + public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder) + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddRuntimeInstrumentation() + .AddBuiltInMeters(); + }) + .WithTracing(tracing => + { + if (builder.Environment.IsDevelopment()) + { + // We want to view all traces in development + tracing.SetSampler(new AlwaysOnSampler()); + } + + tracing.AddAspNetCoreInstrumentation() + .AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder) + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.Configure(logging => logging.AddOtlpExporter()); + builder.Services.ConfigureOpenTelemetryMeterProvider(metrics => metrics.AddOtlpExporter()); + builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter()); + } + + // Uncomment the following lines to enable the Prometheus exporter (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package) + // builder.Services.AddOpenTelemetry() + // .WithMetrics(metrics => metrics.AddPrometheusExporter()); + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.Exporter package) + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + + return builder; + } + + public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder) + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Uncomment the following line to enable the Prometheus endpoint (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package) + // app.MapPrometheusScrapingEndpoint(); + + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/readiness"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + + return app; + } + + private static MeterProviderBuilder AddBuiltInMeters(this MeterProviderBuilder meterProviderBuilder) => + meterProviderBuilder.AddMeter( + "Microsoft.AspNetCore.Hosting", + "Microsoft.AspNetCore.Server.Kestrel", + "System.Net.Http"); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/ServiceDefaults/ServiceDefaults.csproj dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/ServiceDefaults/ServiceDefaults.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/samples/eShopLite/ServiceDefaults/ServiceDefaults.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/samples/eShopLite/ServiceDefaults/ServiceDefaults.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,27 @@ + + + + Library + net8.0 + + + + + + + + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/SECURITY.md dotnet8-8.0.100-8.0.0/src/aspire/SECURITY.md --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/SECURITY.md 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/SECURITY.md 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/AllocatedEndpointAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/AllocatedEndpointAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/AllocatedEndpointAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/AllocatedEndpointAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,63 @@ +// 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; +using System.Net.Sockets; + +namespace Aspire.Hosting.ApplicationModel; + +[DebuggerDisplay("Type = {GetType().Name,nq}, Name = {Name}, UriString = {UriString}, BindingNameQualifiedUriString = {BindingNameQualifiedUriString}")] +public class AllocatedEndpointAnnotation : IResourceAnnotation +{ + public AllocatedEndpointAnnotation(string name, ProtocolType protocol, string address, int port, string scheme) + { + ArgumentNullException.ThrowIfNullOrEmpty(name); + ArgumentNullException.ThrowIfNullOrEmpty(scheme); + ArgumentOutOfRangeException.ThrowIfLessThan(port, 1, nameof(port)); + ArgumentOutOfRangeException.ThrowIfGreaterThan(port, 65535, nameof(port)); + + Name = name; + Protocol = protocol; + Address = address; + Port = port; + UriScheme = scheme; + } + + /// + /// Friendly name for the endpoint. + /// + public string Name { get; private set; } + + /// + /// The network protocol (TCP and UDP are supported). + /// + public ProtocolType Protocol { get; private set; } + + /// + /// The address of the endpoint + /// + public string Address { get; private set; } + + /// + /// The port used by the endpoint + /// + public int Port { get; private set; } + + /// + /// For URI-addressed services, contains the scheme part of the address. + /// + public string UriScheme { get; private set; } + + public string EndPointString => $"{Address}:{Port}"; + + /// + /// URI in string representation specially formatted to be processed by service discovery without ambiguity. + /// + public string BindingNameQualifiedUriString => $"{UriScheme}://_{Name}.{EndPointString}"; + + /// + /// URI in string representation. + /// + public string UriString => $"{UriScheme}://{EndPointString}"; + public override string ToString() => UriString; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ContainerImageAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ContainerImageAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ContainerImageAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ContainerImageAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,14 @@ +// 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; + +namespace Aspire.Hosting.ApplicationModel; + +[DebuggerDisplay("Type = {GetType().Name,nq}, Image = {Image}, Tag = {Tag}")] +public sealed class ContainerImageAnnotation : IResourceAnnotation +{ + public string? Registry { get; set; } + public required string Image { get; set; } + public required string Tag { get; set; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ContainerResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ContainerResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ContainerResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ContainerResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public class ContainerResource(string name) : Resource(name), IResourceWithEnvironment, IResourceWithBindings +{ +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/DcpResourceAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/DcpResourceAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/DcpResourceAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/DcpResourceAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public class DcpResourceAnnotation(string resourceNamespace, string resourceName, string resourceKind) : IResourceAnnotation +{ + public string ResourceNamespace { get; } = resourceNamespace ?? ""; + public string ResourceName { get; } = resourceName; + public string ResourceKind { get; } = resourceKind; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/DistributedApplicationModel.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/DistributedApplicationModel.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/DistributedApplicationModel.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/DistributedApplicationModel.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,14 @@ +// 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; + +namespace Aspire.Hosting.ApplicationModel; + +[DebuggerDisplay("Name = {Name}, Resources = {Resources.Count}")] +public class DistributedApplicationModel(IResourceCollection resources) +{ + public IResourceCollection Resources { get; } = resources; + + public string? Name { get; set; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/EndpointReference.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public sealed class EndpointReference(IResourceWithBindings owner, string bindingName) +{ + public IResourceWithBindings Owner { get; } = owner; + public string BindingName { get; } = bindingName; + + public string UriString + { + get + { + var allocatedEndpoint = Owner.Annotations.OfType().SingleOrDefault(a => a.Name == BindingName); + return allocatedEndpoint?.UriString ?? $"{{{Owner.Name}.bindings.{BindingName}.url}}"; + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/EnvironmentCallbackAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/EnvironmentCallbackAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/EnvironmentCallbackAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/EnvironmentCallbackAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public class EnvironmentCallbackAnnotation : IResourceAnnotation +{ + public EnvironmentCallbackAnnotation(string name, Func callback) + { + Callback = (c) => c.EnvironmentVariables[name] = callback(); + } + + public EnvironmentCallbackAnnotation(Action> callback) + { + Callback = (c) => callback(c.EnvironmentVariables); + } + + public EnvironmentCallbackAnnotation(Action callback) + { + Callback = callback; + } + + public Action Callback { get; private set; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/EnvironmentCallbackContext.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/EnvironmentCallbackContext.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/EnvironmentCallbackContext.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/EnvironmentCallbackContext.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public class EnvironmentCallbackContext(string publisherName, Dictionary? environmentVariables = null) +{ + public Dictionary EnvironmentVariables { get; } = environmentVariables ?? new(); + public string PublisherName { get; } = publisherName; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ExecutableArgsCallbackAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ExecutableArgsCallbackAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ExecutableArgsCallbackAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ExecutableArgsCallbackAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public class ExecutableArgsCallbackAnnotation : IResourceAnnotation +{ + public ExecutableArgsCallbackAnnotation(Action> callback) + { + Callback = callback; + } + + public Action> Callback { get; private set; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ExecutableResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ExecutableResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ExecutableResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ExecutableResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public class ExecutableResource(string name, string command, string workingDirectory, string[]? args) : Resource(name), IResourceWithEnvironment, IResourceWithBindings +{ + public string Command { get; } = command; + public string WorkingDirectory { get; } = workingDirectory; + public string[]? Args { get; } = args; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/Http2ServiceAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/Http2ServiceAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/Http2ServiceAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/Http2ServiceAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// Internal marker +/// +internal sealed class Http2ServiceAnnotation : IResourceAnnotation +{ +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public interface IResourceAnnotation +{ +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceBuilder.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceBuilder.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceBuilder.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceBuilder.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public interface IResourceBuilder where T : IResource +{ + IDistributedApplicationBuilder ApplicationBuilder { get; } + T Resource { get; } + IResourceBuilder WithAnnotation() where TAnnotation : IResourceAnnotation, new() => WithAnnotation(new TAnnotation()); + IResourceBuilder WithAnnotation(TAnnotation annotation) where TAnnotation : IResourceAnnotation; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceCollection.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceCollection.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceCollection.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceCollection.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public interface IResourceCollection : IList +{ +} + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public interface IResource +{ + string Name { get; } + ResourceMetadataCollection Annotations { get; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithBindings.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithBindings.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithBindings.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithBindings.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public interface IResourceWithBindings : IResource +{ + public EndpointReference GetEndpoint(string bindingName) + { + return new EndpointReference(this, bindingName); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithConnectionString.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithConnectionString.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithConnectionString.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithConnectionString.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public interface IResourceWithConnectionString : IResource +{ + public string? GetConnectionString(); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithEnvironment.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithEnvironment.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithEnvironment.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithEnvironment.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public interface IResourceWithEnvironment : IResource +{ +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithParentOfT.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithParentOfT.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithParentOfT.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/IResourceWithParentOfT.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public interface IResourceWithParent : IResource where T : IResource +{ + public T Parent { get; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/LaunchProfileAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/LaunchProfileAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/LaunchProfileAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/LaunchProfileAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,20 @@ +// 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; + +namespace Aspire.Hosting.ApplicationModel; + +[DebuggerDisplay("Type = {GetType().Name,nq}, LaunchProfileName = {LaunchProfileName}")] +internal sealed class LaunchProfileAnnotation : IResourceAnnotation +{ + public LaunchProfileAnnotation(string launchProfileName, LaunchProfile launchProfile) + { + LaunchProfileName = launchProfileName; + LaunchProfile = launchProfile; + } + + public string LaunchProfileName { get; } + + public LaunchProfile LaunchProfile { get; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ManifestPublishingCallbackAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ManifestPublishingCallbackAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ManifestPublishingCallbackAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ManifestPublishingCallbackAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; + +namespace Aspire.Hosting.ApplicationModel; + +public class ManifestPublishingCallbackAnnotation(Action? callback) : IResourceAnnotation +{ + public Action? Callback { get; } = callback; + public static ManifestPublishingCallbackAnnotation Ignore { get; } = new ManifestPublishingCallbackAnnotation(null); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ProjectResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ProjectResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ProjectResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ProjectResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public class ProjectResource(string name) : Resource(name), IResourceWithEnvironment, IResourceWithBindings +{ +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ProjectResourceExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ProjectResourceExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ProjectResourceExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ProjectResourceExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,26 @@ +// 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.CodeAnalysis; + +namespace Aspire.Hosting.ApplicationModel; + +public static class ProjectResourceExtensions +{ + public static IEnumerable GetProjectResources(this DistributedApplicationModel model) + { + return model.Resources.OfType(); + } + + public static bool TryGetProjectWithPath(this DistributedApplicationModel model, string path, [NotNullWhen(true)] out ProjectResource? projectResource) + { + projectResource = model.GetProjectResources().SingleOrDefault(p => p.Annotations.OfType().FirstOrDefault()?.ProjectPath == path); + + return projectResource is not null; + } + + public static IServiceMetadata GetServiceMetadata(this ProjectResource projectResource) + { + return projectResource.Annotations.OfType().Single(); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ReplicaAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ReplicaAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ReplicaAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ReplicaAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,17 @@ +// 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; + +namespace Aspire.Hosting.ApplicationModel; + +[DebuggerDisplay("Type = {GetType().Name,nq}, Replicas = {Replicas}")] +public sealed class ReplicaAnnotation : IResourceAnnotation +{ + public ReplicaAnnotation(int replicas = 1) + { + Replicas = replicas; + } + + public int Replicas { get; private set; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ResourceCollection.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ResourceCollection.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ResourceCollection.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ResourceCollection.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Diagnostics; + +namespace Aspire.Hosting.ApplicationModel; + +[DebuggerDisplay("Count = {Count}")] +[DebuggerTypeProxy(typeof(ApplicationResourceCollectionDebugView))] +internal sealed class ResourceCollection : IResourceCollection +{ + private readonly List _resources = []; + + public IResource this[int index] { get => _resources[index]; set => _resources[index] = value; } + public int Count => _resources.Count; + public bool IsReadOnly => false; + public void Add(IResource item) => _resources.Add(item); + public void Clear() => _resources.Clear(); + public bool Contains(IResource item) => _resources.Contains(item); + public void CopyTo(IResource[] array, int arrayIndex) => _resources.CopyTo(array, arrayIndex); + public IEnumerator GetEnumerator() => _resources.GetEnumerator(); + public int IndexOf(IResource item) => _resources.IndexOf(item); + public void Insert(int index, IResource item) => _resources.Insert(index, item); + public bool Remove(IResource item) => _resources.Remove(item); + public void RemoveAt(int index) => _resources.RemoveAt(index); + IEnumerator IEnumerable.GetEnumerator() => _resources.GetEnumerator(); + + private sealed class ApplicationResourceCollectionDebugView(ResourceCollection collection) + { + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public IResource[] Items => collection.ToArray(); + } +} + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/Resource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/Resource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/Resource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/Resource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,18 @@ +// 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; + +namespace Aspire.Hosting.ApplicationModel; + +[DebuggerDisplay("{DebuggerToString(),nq}")] +public abstract class Resource(string name) : IResource +{ + public string Name { get; } = name; + public ResourceMetadataCollection Annotations { get; } = new ResourceMetadataCollection(); + + private string DebuggerToString() + { + return $@"Type = {GetType().Name}, Name = ""{Name}"""; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ResourceMetadataCollection.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ResourceMetadataCollection.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ResourceMetadataCollection.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ResourceMetadataCollection.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.ObjectModel; + +namespace Aspire.Hosting.ApplicationModel; + +public sealed class ResourceMetadataCollection : Collection +{ +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,65 @@ +// 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; +using System.Net.Sockets; + +namespace Aspire.Hosting.ApplicationModel; + +[DebuggerDisplay("Type = {GetType().Name,nq}, Name = {Name}")] +public sealed class ServiceBindingAnnotation : IResourceAnnotation +{ + public ServiceBindingAnnotation(ProtocolType protocol, string? uriScheme = null, string? transport = null, string? name = null, int? port = null, int? containerPort = null, bool? isExternal = null) + { + // If the URI scheme is null, we'll adopt either udp:// or tcp:// based on the + // protocol. If the name is null, we'll use the URI scheme as the default. This + // is because we eventually always need these values to be populated so lets do + // it up front. + + Protocol = protocol; + UriScheme = uriScheme ?? protocol.ToString().ToLowerInvariant(); + Transport = transport ?? (UriScheme == "http" || UriScheme == "https" ? "http" : Protocol.ToString().ToLowerInvariant()); + Name = name ?? UriScheme; + Port = port; + ContainerPort = containerPort ?? port; + IsExternal = isExternal ?? false; + } + + /// + /// Name of the service + /// + public string Name { get; internal set; } + + /// + /// Network protocol: TCP or UDP are supported today, others possibly in future. + /// + public ProtocolType Protocol { get; internal set; } + + /// + /// Desired port for the service + /// + public int? Port { get; internal set; } + + /// + /// If the binding is used for the container, this is the port the container process is listening on. + /// + /// + /// Defaults to . + /// + public int? ContainerPort { get; internal set; } + + /// + /// If a service is URI-addressable, this will property will contain the URI scheme to use for constructing service URI. + /// + public string UriScheme { get; internal set; } + + /// + /// Transport that is being used (e.g. http, http2, http3 etc). + /// + public string Transport { get; internal set; } + + /// + /// Indicates that this service binding should be exposed externally at publish time. + /// + public bool IsExternal { get; internal set; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingAnnotationExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingAnnotationExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingAnnotationExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingAnnotationExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public static class ServiceBindingAnnotationExtensions +{ + public static ServiceBindingAnnotation AsHttp2(this ServiceBindingAnnotation binding) + { + binding.Transport = "http2"; + binding.UriScheme = "https"; + return binding; + } + + public static ServiceBindingAnnotation AsExternal(this ServiceBindingAnnotation binding) + { + binding.IsExternal = true; + return binding; + } + +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingCallbackAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingCallbackAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingCallbackAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingCallbackAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +internal sealed class ServiceBindingCallbackAnnotation(string publisherName, string bindingName, Func? callback) : IResourceAnnotation +{ + public string PublisherName { get; } = publisherName; + public string BindingName { get; } = bindingName; + public Func? Callback { get; } = callback; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingCallbackContext.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingCallbackContext.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingCallbackContext.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceBindingCallbackContext.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public class ServiceBindingCallbackContext(string publisherName, ServiceBindingAnnotation binding) +{ + public string PublisherName { get; } = publisherName; + public ServiceBindingAnnotation Binding { get; } = binding; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceReferenceAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceReferenceAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceReferenceAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/ServiceReferenceAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +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.Collections.ObjectModel; + +namespace Aspire.Hosting.ApplicationModel; + +internal sealed class ServiceReferenceAnnotation(IResource resource) : IResourceAnnotation +{ + public IResource Resource { get; } = resource; + public bool UseAllBindings { get; set; } + public Collection BindingNames { get; } = new(); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/VolumeMountAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/VolumeMountAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/ApplicationModel/VolumeMountAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/ApplicationModel/VolumeMountAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,29 @@ +// 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; + +namespace Aspire.Hosting.ApplicationModel; + +[DebuggerDisplay("Type = {GetType().Name,nq}, Source = {Source}, Target = {Target}")] +public sealed class VolumeMountAnnotation : IResourceAnnotation +{ + public VolumeMountAnnotation(string source, string target, VolumeMountType type = default, bool isReadOnly = false) + { + Source = source; + Target = target; + Type = type; + IsReadOnly = isReadOnly; + } + + public string Source { get; set; } + public string Target { get; set; } + public VolumeMountType Type { get; set; } + public bool IsReadOnly { get; set; } +} + +public enum VolumeMountType +{ + Bind, + Named +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/AspireEventSource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/AspireEventSource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/AspireEventSource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/AspireEventSource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,265 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting; + +using System.Diagnostics.Tracing; +using Aspire.Hosting.Dcp; + +[EventSource(Name = "Microsoft-Aspire-Hosting")] +internal sealed class AspireEventSource : EventSource +{ + public static readonly AspireEventSource Instance = new AspireEventSource(); + + [Event(1, Level = EventLevel.Informational, Message = "DCP host is starting...")] + public void DcpHostStartupStart() + { + if (IsEnabled()) + { + WriteEvent(1); + } + } + + [Event(2, Level = EventLevel.Informational, Message = "DCP host has started")] + public void DcpHostStartupStop() + { + if (IsEnabled()) + { + WriteEvent(2); + } + } + + [Event(3, Level = EventLevel.Informational, Message = "Docker health check is starting...")] + public void DockerHealthCheckStart() + { + if (IsEnabled()) + { + WriteEvent(3); + } + } + + [Event(4, Level = EventLevel.Informational, Message = "Docker health check completed")] + public void DockerHealthCheckStop() + { + if (IsEnabled()) + { + WriteEvent(4); + } + } + + [Event(5, Level = EventLevel.Informational, Message = "DistributedApplication build is starting...")] + public void DistributedApplicationBuildStart() + { + if (IsEnabled()) + { + WriteEvent(5); + } + } + + [Event(6, Level = EventLevel.Informational, Message = "DistributedApplication build completed")] + public void DistributedApplicationBuildStop() + { + if (IsEnabled()) + { + WriteEvent(6); + } + } + + [Event(7, Level = EventLevel.Informational, Message = "DCP API server is starting...")] + public void DcpApiServerLaunchStart() + { + if (IsEnabled()) + { + WriteEvent(7); + } + } + + [Event(8, Level = EventLevel.Informational, Message = "DCP API server has started")] + public void DcpApiServerLaunchStop() + { + if (IsEnabled()) + { + WriteEvent(8); + } + } + + [Event(9, Level = EventLevel.Informational, Message = "DCP logging socket is being created...")] + public void DcpLogSocketCreateStart() + { + if (IsEnabled()) + { + WriteEvent(9); + } + } + + [Event(10, Level = EventLevel.Informational, Message = "DCP logging socket has been created")] + public void DcpLogSocketCreateStop() + { + if (IsEnabled()) + { + WriteEvent(10); + } + } + + [Event(11, Level = EventLevel.Informational, Message = "A process is starting...")] + public void ProcessLaunchStart(string executablePath, string arguments) + { + if (IsEnabled()) + { + WriteEvent(11, executablePath, arguments); + } + } + + [Event(12, Level = EventLevel.Informational, Message = "Process has been started")] + public void ProcessLaunchStop(string executablePath, string arguments) + { + if (IsEnabled()) + { + WriteEvent(12, executablePath, arguments); + } + } + + [Event(13, Level = EventLevel.Informational, Message = "DCP API call starting...")] + public void DcpApiCallStart(DcpApiOperationType operationType, string resourceType) + { + if (IsEnabled()) + { + WriteEvent(13, operationType, resourceType); + } + } + + [Event(14, Level = EventLevel.Informational, Message = "DCP API call completed")] + public void DcpApiCallStop(DcpApiOperationType operationType, string resourceType) + { + if (IsEnabled()) + { + WriteEvent(14, operationType, resourceType); + } + } + + [Event(15, Level = EventLevel.Informational, Message = "DCP API call is being retried...")] + public void DcpApiCallRetry(DcpApiOperationType operationType, string resourceType) + { + if (IsEnabled()) + { + WriteEvent(15, operationType, resourceType); + } + } + + [Event(16, Level = EventLevel.Error, Message = "DCP API call timed out")] + public void DcpApiCallTimeout(DcpApiOperationType operationType, string resourceType) + { + if (IsEnabled()) + { + WriteEvent(16, operationType, resourceType); + } + } + + [Event(17, Level = EventLevel.Informational, Message = "DCP application model creation starting...")] + public void DcpModelCreationStart() + { + if (IsEnabled()) + { + WriteEvent(17); + } + } + + [Event(18, Level = EventLevel.Informational, Message = "DCP application model creation completed")] + public void DcpModelCreationStop() + { + if (IsEnabled()) + { + WriteEvent(18); + } + } + + [Event(19, Level = EventLevel.Informational, Message = "DCP Service object creation starting...")] + public void DcpServicesCreationStart() + { + if (IsEnabled()) + { + WriteEvent(19); + } + } + + [Event(20, Level = EventLevel.Informational, Message = "DCP Service object creation completed")] + public void DcpServicesCreationStop() + { + if (IsEnabled()) + { + WriteEvent(20); + } + } + + [Event(21, Level = EventLevel.Informational, Message = "DCP Container object creation starting...")] + public void DcpContainersCreateStart() + { + if (IsEnabled()) + { + WriteEvent(21); + } + } + + [Event(22, Level = EventLevel.Informational, Message = "DCP Container object creation completed")] + public void DcpContainersCreateStop() + { + if (IsEnabled()) + { + WriteEvent(22); + } + } + + [Event(23, Level = EventLevel.Informational, Message = "DCP Executable object creation starting...")] + public void DcpExecutablesCreateStart() + { + if (IsEnabled()) + { + WriteEvent(23); + } + } + + [Event(24, Level = EventLevel.Informational, Message = "DCP Executable object creation completed")] + public void DcpExecutablesCreateStop() + { + if (IsEnabled()) + { + WriteEvent(24); + } + } + + [Event(25, Level = EventLevel.Informational, Message = "DCP application model cleanup starting...")] + public void DcpModelCleanupStart() + { + if (IsEnabled()) + { + WriteEvent(25); + } + } + + [Event(26, Level = EventLevel.Informational, Message = "DCP application model cleanup completed")] + public void DcpModelCleanupStop() + { + if (IsEnabled()) + { + WriteEvent(26); + } + } + + [Event(27, Level = EventLevel.Informational, Message = "Application before-start hooks running...")] + public void AppBeforeStartHooksStart() + { + if (IsEnabled()) + { + WriteEvent(27); + } + } + + [Event(28, Level = EventLevel.Informational, Message = "Application before-start hooks completed")] + public void AppBeforeStartHooksStop() + { + if (IsEnabled()) + { + WriteEvent(28); + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Aspire.Hosting.csproj dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Aspire.Hosting.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Aspire.Hosting.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Aspire.Hosting.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,44 @@ + + + + $(NetCurrent) + Library + true + true + $(NoWarn);CS8002 + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/build/Aspire.Hosting.props dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/build/Aspire.Hosting.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/build/Aspire.Hosting.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/build/Aspire.Hosting.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,22 @@ + + + + + false + false + + + + + + + + + + false + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/build/Aspire.Hosting.targets dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/build/Aspire.Hosting.targets --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/build/Aspire.Hosting.targets 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/build/Aspire.Hosting.targets 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,126 @@ + + + + + + + $(MSBuildProjectDirectory) + <_AspireIntermediatePath>$(IntermediateOutputPath)Aspire\ + + + + + + + + + + + $([System.IO.Path]::GetFileNameWithoutExtension(%(ReferencePathWithRefAssemblies.Identity)).Replace(".", "_").Replace("-","_")) + $([System.String]::Copy(%(ReferencePathWithRefAssemblies.ServiceNameOverride)).Replace(".", "_").Replace("-","_")) + $([System.IO.Path]::GetFileNameWithoutExtension(%(ReferencePathWithRefAssemblies.Identity))) + $([System.IO.Path]::GetFullPath(%(ReferencePathWithRefAssemblies.ProjectReferenceOriginalItemSpec))) + Projects + + + + + + + + + %(Namespace)%(ClassName) """]]>%(AssemblyName) """]]>%(Identity) """]]>%(ProjectPath) + + + + + + + + + + + + + + + + + + <_WriteServiceMetadataSourcesDependsOn>_CSharpWriteServiceMetadataSources;_WarnOnUnsupportedLanguage + + + + + + + + + $([MSBuild]::NormalizePath($([System.Environment]::GetFolderPath(SpecialFolder.UserProfile)), '.dcp')) + $([MSBuild]::NormalizePath($(DcpDir), 'ext')) + $([MSBuild]::NormalizePath($(DcpExtensionsPath), 'bin')) + $([MSBuild]::NormalizePath($(DcpDir), 'dcp')) + $(DcpCliPath).exe + + + + + <_Parameter1>dcpclipath + <_Parameter2>$(DcpCliPath) + + + <_Parameter1>dcpextensionpaths + <_Parameter2>$(DcpExtensionsPath) + + + <_Parameter1>dcpbinpath + <_Parameter2>$(DcpBinPath) + + + + + + manifest + $(_AspireIntermediatePath)manifest.json + + + + + + <_AspireManifestPublishArg Include="%22$(DOTNET_HOST_PATH)%22; %22$(TargetPath)%22;"/> + <_AspireManifestPublishArg Include="--publisher; $(AspirePublisher);" /> + <_AspireManifestPublishArg Include="--output-path; %22$(AspireManifestPublishOutputPath)%22" /> + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/buildMultiTargeting/Aspire.Hosting.props dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/buildMultiTargeting/Aspire.Hosting.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/buildMultiTargeting/Aspire.Hosting.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/buildMultiTargeting/Aspire.Hosting.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,3 @@ + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/buildMultiTargeting/Aspire.Hosting.targets dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/buildMultiTargeting/Aspire.Hosting.targets --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/buildMultiTargeting/Aspire.Hosting.targets 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/buildMultiTargeting/Aspire.Hosting.targets 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,3 @@ + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/buildTransitive/Aspire.Hosting.props dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/buildTransitive/Aspire.Hosting.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/buildTransitive/Aspire.Hosting.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/buildTransitive/Aspire.Hosting.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,3 @@ + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/buildTransitive/Aspire.Hosting.targets dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/buildTransitive/Aspire.Hosting.targets --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/buildTransitive/Aspire.Hosting.targets 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/buildTransitive/Aspire.Hosting.targets 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,3 @@ + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/ConsoleLogsConfigurationExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/ConsoleLogsConfigurationExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/ConsoleLogsConfigurationExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/ConsoleLogsConfigurationExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Dashboard.ConsoleLogs; +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting.Dashboard; + +public static class ConsoleLogsConfigurationExtensions +{ + public static IResourceBuilder ConfigureConsoleLogs(this IResourceBuilder builder) where T : IResourceWithEnvironment + { + return builder.WithEnvironment((context) => + { + if (context.PublisherName == "manifest") + { + return; + } + // Enable ANSI Control Sequences for colors in Output Redirection + context.EnvironmentVariables["DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION"] = "true"; + + // Enable Simple Console Logger Formatting with a UTC timestamp similar to RFC3339Nano that Docker generates + context.EnvironmentVariables["LOGGING__CONSOLE__FORMATTERNAME"] = "simple"; + context.EnvironmentVariables["LOGGING__CONSOLE__FORMATTEROPTIONS__TIMESTAMPFORMAT"] = $"{TimestampParser.DisplayFormat} "; + }); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/ContainerViewModelCache.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/ContainerViewModelCache.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/ContainerViewModelCache.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/ContainerViewModelCache.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Dashboard.Model; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Dcp; +using Aspire.Hosting.Dcp.Model; + +namespace Aspire.Hosting.Dashboard; + +internal sealed class ContainerViewModelCache : ViewModelCache +{ + public ContainerViewModelCache( + KubernetesService kubernetesService, DistributedApplicationModel applicationModel, CancellationToken cancellationToken) + : base(kubernetesService, applicationModel, cancellationToken) + { + } + + protected override ContainerViewModel ConvertToViewModel( + DistributedApplicationModel applicationModel, + IEnumerable services, + IEnumerable endpoints, + Container container, + List? additionalEnvVars) + { + var model = new ContainerViewModel + { + Name = container.Metadata.Name, + Uid = container.Metadata.Uid, + NamespacedName = new(container.Metadata.Name, null), + ContainerId = container.Status?.ContainerId, + CreationTimeStamp = container.Metadata.CreationTimestamp?.ToLocalTime(), + Image = container.Spec.Image!, + LogSource = new DockerContainerLogSource(container.Status!.ContainerId!), + State = container.Status?.State, + ExpectedEndpointsCount = GetExpectedEndpointsCount(services, container) + }; + + if (container.Spec.Ports != null) + { + foreach (var port in container.Spec.Ports) + { + if (port.ContainerPort != null) + { + model.Ports.Add(port.ContainerPort.Value); + } + } + } + + FillEndpoints(applicationModel, services, endpoints, container, model); + + if (additionalEnvVars is not null) + { + FillEnvironmentVariables(model.Environment, additionalEnvVars, additionalEnvVars); + } + else if (container.Spec.Env is not null) + { + FillEnvironmentVariables(model.Environment, container.Spec.Env, container.Spec.Env); + } + + return model; + } + + protected override bool FilterResource(Container resource) => true; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/DashboardViewModelService.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/DashboardViewModelService.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/DashboardViewModelService.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/DashboardViewModelService.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using Aspire.Dashboard.Model; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Dcp; +using Aspire.Hosting.Dcp.Model; +using Microsoft.Extensions.Hosting; +using NamespacedName = Aspire.Dashboard.Model.NamespacedName; + +namespace Aspire.Hosting.Dashboard; + +internal sealed partial class DashboardViewModelService : IDashboardViewModelService, IAsyncDisposable +{ + private const string AppHostSuffix = ".AppHost"; + + private readonly DistributedApplicationModel _applicationModel; + private readonly string _applicationName; + private readonly CancellationTokenSource _cancellationTokenSource = new(); + + private readonly ViewModelCache _containerViewModelCache; + private readonly ViewModelCache _executableViewModelCache; + private readonly ViewModelCache _projectViewModelCache; + + public DashboardViewModelService(DistributedApplicationModel applicationModel, KubernetesService kubernetesService, IHostEnvironment hostEnvironment) + { + _applicationModel = applicationModel; + _applicationName = ComputeApplicationName(hostEnvironment.ApplicationName); + _containerViewModelCache = new ContainerViewModelCache( + kubernetesService, + _applicationModel, + _cancellationTokenSource.Token); + _executableViewModelCache = new ExecutableViewModelCache( + kubernetesService, + _applicationModel, + _cancellationTokenSource.Token); + _projectViewModelCache = new ProjectViewModelCache( + kubernetesService, + _applicationModel, + _cancellationTokenSource.Token); + } + + public string ApplicationName => _applicationName; + + public ValueTask> GetContainersAsync() => _containerViewModelCache.GetResourcesAsync(); + public ValueTask> GetExecutablesAsync() => _executableViewModelCache.GetResourcesAsync(); + public ValueTask> GetProjectsAsync() => _projectViewModelCache.GetResourcesAsync(); + + public async IAsyncEnumerable> WatchContainersAsync( + IEnumerable? existingContainers = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (var item in _containerViewModelCache.WatchResourceAsync(existingContainers, cancellationToken)) + { + yield return item; + } + } + + public async IAsyncEnumerable> WatchExecutablesAsync( + IEnumerable? existingExecutables = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (var item in _executableViewModelCache.WatchResourceAsync(existingExecutables, cancellationToken)) + { + yield return item; + } + } + + public async IAsyncEnumerable> WatchProjectsAsync( + IEnumerable? existingProjects = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (var item in _projectViewModelCache.WatchResourceAsync(existingProjects, cancellationToken)) + { + yield return item; + } + } + + public async ValueTask DisposeAsync() + { + await _cancellationTokenSource.CancelAsync().ConfigureAwait(false); + } + + private static string ComputeApplicationName(string applicationName) + { + if (applicationName.EndsWith(AppHostSuffix, StringComparison.OrdinalIgnoreCase)) + { + applicationName = applicationName[..^AppHostSuffix.Length]; + } + + return applicationName; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/DockerContainerLogSource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/DockerContainerLogSource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/DockerContainerLogSource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/DockerContainerLogSource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,227 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Threading.Channels; +using Aspire.Dashboard.Model; +using Aspire.Hosting.Dcp.Process; +using Aspire.Hosting.Utils; + +namespace Aspire.Hosting.Dashboard; + +internal sealed class DockerContainerLogSource(string containerId) : ILogSource +{ + private readonly string _containerId = containerId; + private DockerContainerLogWatcher? _containerLogWatcher; + + public async ValueTask StartAsync(CancellationToken cancellationToken) + { + if (_containerLogWatcher is not null) + { + return true; + } + + _containerLogWatcher = new DockerContainerLogWatcher(_containerId); + var watcherInitialized = await _containerLogWatcher.InitWatchAsync(cancellationToken).ConfigureAwait(false); + if (!watcherInitialized) + { + await _containerLogWatcher.DisposeAsync().ConfigureAwait(false); + } + return watcherInitialized; + } + + public async IAsyncEnumerable WatchOutputLogAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + if (_containerLogWatcher is not null) + { + await foreach (var logs in _containerLogWatcher!.WatchOutputLogsAsync(cancellationToken).ConfigureAwait(false)) + { + yield return logs; + } + } + } + + public async IAsyncEnumerable WatchErrorLogAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + if (_containerLogWatcher is not null) + { + await foreach (var logs in _containerLogWatcher!.WatchErrorLogsAsync(cancellationToken).ConfigureAwait(false)) + { + yield return logs; + } + } + } + + public async ValueTask StopAsync(CancellationToken cancellationToken = default) + { + if (_containerLogWatcher is not null) + { + await _containerLogWatcher.DisposeAsync().ConfigureAwait(false); + _containerLogWatcher = null; + } + } + + private sealed class DockerContainerLogWatcher(string? containerId) : IAsyncDisposable + { + private const string Executable = "docker"; + + private readonly Channel _outputChannel = Channel.CreateUnbounded(new UnboundedChannelOptions() { SingleReader = true }); + private readonly Channel _errorChannel = Channel.CreateUnbounded(new UnboundedChannelOptions() { SingleReader = true }); + + private IAsyncDisposable? _processDisposable; + + public async Task InitWatchAsync(CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(containerId)) + { + return false; + } + + Task? processResultTask = null; + + try + { + var args = $"logs --follow -t {containerId}"; + + var spec = new ProcessSpec(FileUtil.FindFullPathFromPath(Executable)) + { + Arguments = args, + OnOutputData = WriteToOutputChannel, + OnErrorData = WriteToErrorChannel, + KillEntireProcessTree = false, + ThrowOnNonZeroReturnCode = false // We don't want this to throw an exception because it is common + // for us to cancel the task and kill the process, which returns -1 + }; + + (processResultTask, _processDisposable) = ProcessUtil.Run(spec); + + var tcs = new TaskCompletionSource(); + + // Make sure the process exits if the cancellation token is cancelled + var ctr = cancellationToken.Register(() => tcs.TrySetResult()); + + // Don't forward cancellationToken here, because its handled internally in WaitForExit + _ = Task.Run(() => WaitForExit(tcs, ctr), CancellationToken.None); + + return true; + } + catch + { + await DisposeProcess().ConfigureAwait(false); + + return false; + } + + async ValueTask DisposeProcess() + { + if (_processDisposable is not null) + { + await _processDisposable.DisposeAsync().ConfigureAwait(false); + _processDisposable = null; + } + } + + void WriteToOutputChannel(string s) + { + _outputChannel.Writer.TryWrite(s); + } + + void WriteToErrorChannel(string s) + { + _errorChannel.Writer.TryWrite(s); + } + + async Task WaitForExit(TaskCompletionSource tcs, CancellationTokenRegistration ctr) + { + if (processResultTask is not null) + { + // Wait for cancellation (tcs.Task) or for the process itself to exit. + await Task.WhenAny(tcs.Task, processResultTask).ConfigureAwait(false); + + if (processResultTask.IsCompleted) + { + // If it was the process that exited, write that out to the logs. If it was cancelled, + // there's no need to because the user has left the page + var processResult = processResultTask.Result; + await _outputChannel.Writer.WriteAsync($"Process exited with code {processResult.ExitCode}", cancellationToken).ConfigureAwait(false); + } + + _outputChannel.Writer.Complete(); + _errorChannel.Writer.Complete(); + + // If the process has already exited, this will be a no-op. But if it was cancelled + // we need to end the process + await DisposeProcess().ConfigureAwait(false); + } + + ctr.Unregister(); + } + } + + public async IAsyncEnumerable WatchOutputLogsAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + List currentLogs = []; + + // Wait until there's something to read + if (await _outputChannel.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + // And then read everything that there is to read + while (!cancellationToken.IsCancellationRequested && _outputChannel.Reader.TryRead(out var log)) + { + currentLogs.Add(log); + } + + if (!cancellationToken.IsCancellationRequested && currentLogs.Count > 0) + { + yield return currentLogs.ToArray(); + } + } + else + { + // WaitToReadAsync will return false when the Channel is marked Complete + // down in WaitForExist, so we'll break out of the loop here + break; + } + } + } + + public async IAsyncEnumerable WatchErrorLogsAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + List currentLogs = []; + + // Wait until there's something to read + if (await _errorChannel.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + // And then read everything that there is to read + while (!cancellationToken.IsCancellationRequested && _errorChannel.Reader.TryRead(out var log)) + { + currentLogs.Add(log); + } + + if (!cancellationToken.IsCancellationRequested && currentLogs.Count > 0) + { + yield return currentLogs.ToArray(); + } + } + else + { + // WaitToReadAsync will return false when the Channel is marked Complete + // down in WaitForExist, so we'll break out of the loop here + break; + } + } + } + + public async ValueTask DisposeAsync() + { + if (_processDisposable is not null) + { + await _processDisposable.DisposeAsync().ConfigureAwait(false); + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/ExecutableViewModelCache.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/ExecutableViewModelCache.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/ExecutableViewModelCache.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/ExecutableViewModelCache.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Dashboard.Model; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Dcp; +using Aspire.Hosting.Dcp.Model; + +namespace Aspire.Hosting.Dashboard; + +internal sealed class ExecutableViewModelCache : ViewModelCache +{ + public ExecutableViewModelCache( + KubernetesService kubernetesService, DistributedApplicationModel applicationModel, CancellationToken cancellationToken) + : base(kubernetesService, applicationModel, cancellationToken) + { + } + + protected override ExecutableViewModel ConvertToViewModel( + DistributedApplicationModel applicationModel, + IEnumerable services, + IEnumerable endpoints, + Executable executable, + List? additionalEnvVars) + { + var model = new ExecutableViewModel + { + Name = executable.Metadata.Name, + Uid = executable.Metadata.Uid, + NamespacedName = new(executable.Metadata.Name, null), + CreationTimeStamp = executable.Metadata.CreationTimestamp?.ToLocalTime(), + ExecutablePath = executable.Spec.ExecutablePath, + WorkingDirectory = executable.Spec.WorkingDirectory, + Arguments = executable.Spec.Args, + State = executable.Status?.State, + LogSource = new FileLogSource(executable.Status?.StdOutFile, executable.Status?.StdErrFile), + ProcessId = executable.Status?.ProcessId, + ExpectedEndpointsCount = GetExpectedEndpointsCount(services, executable) + }; + + FillEndpoints(applicationModel, services, endpoints, executable, model); + + if (executable.Status?.EffectiveEnv is not null) + { + FillEnvironmentVariables(model.Environment, executable.Status.EffectiveEnv, executable.Spec.Env); + } + return model; + } + + protected override bool FilterResource(Executable resource) + => resource.Metadata.Annotations is null || !resource.Metadata.Annotations.ContainsKey(Executable.CSharpProjectPathAnnotation); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/FileLogSource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/FileLogSource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/FileLogSource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/FileLogSource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.IO.Pipelines; +using System.Runtime.CompilerServices; +using System.Text; +using Aspire.Dashboard.Model; + +namespace Aspire.Hosting.Dashboard; + +internal sealed class FileLogSource(string? stdOutPath, string? stdErrPath) : ILogSource +{ + private readonly string? _stdOutPath = stdOutPath; + private readonly string? _stdErrPath = stdErrPath; + + public ValueTask StartAsync(CancellationToken cancellationToken) + { + return ValueTask.FromResult(_stdOutPath is not null || _stdErrPath is not null); + } + + public async IAsyncEnumerable WatchOutputLogAsync([EnumeratorCancellation]CancellationToken cancellationToken) + { + if (_stdOutPath is not null) + { + await foreach (var logs in WatchLogAsync(_stdOutPath, cancellationToken)) + { + yield return logs; + } + } + } + + public async IAsyncEnumerable WatchErrorLogAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + if (_stdErrPath is not null) + { + await foreach (var logs in WatchLogAsync(_stdErrPath, cancellationToken)) + { + yield return logs; + } + } + } + + public ValueTask StopAsync(CancellationToken cancellationToken = default) + { + return ValueTask.CompletedTask; + } + + private static readonly StreamPipeReaderOptions s_streamPipeReaderOptions = new(leaveOpen: true); + private static readonly string[] s_lineSeparators = ["\r", "\n", "\r\n"]; + private static bool IsNewLine(char c) => c == '\r' || c == '\n'; + + private static async IAsyncEnumerable WatchLogAsync(string filePath, [EnumeratorCancellation] CancellationToken cancellationToken) + { + using var fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + + var partialLine = string.Empty; + + // The FileStream will stay open and continue growing as data is written to it + // but the PipeReader will close as soon as it reaches the end of the FileStream. + // So we need to keep re-creating it. It will read from the last position + while (!cancellationToken.IsCancellationRequested) + { + var reader = PipeReader.Create(fileStream, s_streamPipeReaderOptions); + + while (!cancellationToken.IsCancellationRequested) + { + var result = await reader!.ReadAsync(cancellationToken).ConfigureAwait(false); + + if (result.IsCompleted) + { + break; + } + + var logs = Encoding.UTF8.GetString(result.Buffer); + + // It's possible that we don't read an entire log line at the end of the data we're reading. + // If that's the case, we'll wait for the next iteration, grab the rest and concatenate them. + var lastLineComplete = IsNewLine(logs[^1]); + var lines = logs.Split(s_lineSeparators, StringSplitOptions.RemoveEmptyEntries); + lines[0] = partialLine + lines[0]; + partialLine = lastLineComplete ? string.Empty : lines[^1]; + + var numberOfLinesToSend = lastLineComplete ? lines.Length : lines.Length - 1; + + yield return lines[..numberOfLinesToSend]; // end of range is exclusive + + var position = GetPosition(result.Buffer); + + reader.AdvanceTo(position); + } + + reader.Complete(); + } + + static SequencePosition GetPosition(in ReadOnlySequence buffer) + { + var sequenceReader = new SequenceReader(buffer); + sequenceReader.AdvanceToEnd(); + return sequenceReader.Position; + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/ProjectViewModelCache.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/ProjectViewModelCache.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/ProjectViewModelCache.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/ProjectViewModelCache.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Dashboard.Model; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Dcp; +using Aspire.Hosting.Dcp.Model; + +namespace Aspire.Hosting.Dashboard; + +internal sealed class ProjectViewModelCache : ViewModelCache +{ + public ProjectViewModelCache( + KubernetesService kubernetesService, DistributedApplicationModel applicationModel, CancellationToken cancellationToken) + : base(kubernetesService, applicationModel, cancellationToken) + { + } + + protected override ProjectViewModel ConvertToViewModel( + DistributedApplicationModel applicationModel, + IEnumerable services, + IEnumerable endpoints, + Executable executable, + List? additionalEnvVars) + { + var model = new ProjectViewModel + { + Name = executable.Metadata.Name, + Uid = executable.Metadata.Uid, + NamespacedName = new(executable.Metadata.Name, null), + CreationTimeStamp = executable.Metadata.CreationTimestamp?.ToLocalTime(), + ProjectPath = executable.Metadata.Annotations?[Executable.CSharpProjectPathAnnotation] ?? "", + State = executable.Status?.State, + LogSource = new FileLogSource(executable.Status?.StdOutFile, executable.Status?.StdErrFile), + ProcessId = executable.Status?.ProcessId, + ExpectedEndpointsCount = GetExpectedEndpointsCount(services, executable) + }; + + FillEndpoints(applicationModel, services, endpoints, executable, model); + + if (executable.Status?.EffectiveEnv is not null) + { + FillEnvironmentVariables(model.Environment, executable.Status.EffectiveEnv, executable.Spec.Env); + } + return model; + } + + protected override bool FilterResource(Executable resource) + => resource.Metadata.Annotations?.ContainsKey(Executable.CSharpProjectPathAnnotation) == true; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/ViewModelCache.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/ViewModelCache.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dashboard/ViewModelCache.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dashboard/ViewModelCache.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,539 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Text.Json.Nodes; +using System.Text.Json; +using System.Text; +using System.Threading.Channels; +using Aspire.Dashboard.Model; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Dcp; +using Aspire.Hosting.Dcp.Model; +using Aspire.Hosting.Dcp.Process; +using Aspire.Hosting.Utils; +using k8s; +using NamespacedName = Aspire.Dashboard.Model.NamespacedName; + +namespace Aspire.Hosting.Dashboard; + +internal abstract class ViewModelCache + where TResource : CustomResource + where TViewModel : ResourceViewModel +{ + private readonly KubernetesService _kubernetesService; + private readonly DistributedApplicationModel _applicationModel; + + private readonly object _syncLock = new(); + private readonly Dictionary _resourcesMap = []; + private readonly List> _resourceChanges = []; + private readonly Channel> _publishingChannel; + private readonly List>?> _subscribedChannels = []; + + protected ViewModelCache( + KubernetesService kubernetesService, + DistributedApplicationModel applicationModel, + CancellationToken cancellationToken) + { + _kubernetesService = kubernetesService; + _applicationModel = applicationModel; + _publishingChannel = Channel.CreateUnbounded>(); + + Task.Run(async () => + { + // Start an enumerator which combines underlying kubernetes watches + // And return stream of changes in view model in publishing channel + var enumerator = new ViewModelGeneratingEnumerator( + _kubernetesService, + _applicationModel, + FilterResource, + ConvertToViewModel, + cancellationToken); + + while (await enumerator.MoveNextAsync().ConfigureAwait(false)) + { + var (objectChangeType, resource) = enumerator.Current; + switch (objectChangeType) + { + case ObjectChangeType.Added: + _resourcesMap.Add(resource.Name, resource); + break; + + case ObjectChangeType.Modified: + _resourcesMap[resource.Name] = resource; + break; + + case ObjectChangeType.Deleted: + _resourcesMap.Remove(resource.Name); + break; + } + + await _publishingChannel.Writer.WriteAsync(enumerator.Current, cancellationToken).ConfigureAwait(false); + } + }, cancellationToken); + + Task.Run(async () => + { + // Receive data from publishing channel + // Update snapshot and send data to other subscribers + await foreach (var change in _publishingChannel.Reader.ReadAllAsync(cancellationToken)) + { + Channel>?[] listeningChannels = []; + lock (_syncLock) + { + _resourceChanges.Add(change); + listeningChannels = _subscribedChannels.ToArray(); + } + + foreach (var channel in listeningChannels) + { + if (channel is not null) + { + await channel.Writer.WriteAsync(change, cancellationToken).ConfigureAwait(false); + } + } + } + } + , cancellationToken); + } + + public ValueTask> GetResourcesAsync() + { + return ValueTask.FromResult(_resourcesMap.Values.ToList()); + } + + public async IAsyncEnumerable> WatchResourceAsync( + IEnumerable? existingObjects, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + var listeningChannel = Channel.CreateUnbounded>(); + List> existingChanges = []; + lock (_syncLock) + { + existingChanges = _resourceChanges.ToList(); + _subscribedChannels.Add(listeningChannel); + } + + // We create a new watch based on existing changes and subscribing for few changes + var enumerator = new ViewModelWatchEnumerator(existingChanges, listeningChannel, cancellationToken); + while (await enumerator.MoveNextAsync().ConfigureAwait(false)) + { + var result = enumerator.Current; + if (result.ObjectChangeType == ObjectChangeType.Added + && existingObjects?.Any( + o => string.Equals(o.Name, result.Resource.Name, StringComparison.OrdinalIgnoreCase) + && string.Equals(o.Namespace, result.Resource.NamespacedName.Namespace, StringComparison.OrdinalIgnoreCase)) == true) + { + continue; + } + + yield return result; + } + await enumerator.DisposeAsync().ConfigureAwait(false); + + lock (_syncLock) + { + _subscribedChannels.Remove(listeningChannel); + } + } + + protected abstract bool FilterResource(TResource resource); + + protected abstract TViewModel ConvertToViewModel( + DistributedApplicationModel applicationModel, + IEnumerable services, + IEnumerable endpoints, + TResource resource, + List? additionalEnvVars); + + protected static void FillEndpoints( + DistributedApplicationModel applicationModel, + IEnumerable services, + IEnumerable endpoints, + CustomResource resource, + ResourceViewModel resourceViewModel) + { + resourceViewModel.Endpoints.AddRange( + endpoints.Where(ep => ep.Metadata.OwnerReferences?.Any(or => or.Kind == resource.Kind && or.Name == resource.Metadata.Name) == true) + .Select(ep => + { + var matchingService = services.SingleOrDefault(s => s.Metadata.Name == ep.Spec.ServiceName); + if (matchingService?.Metadata.Annotations?.TryGetValue(CustomResource.UriSchemeAnnotation, out var uriScheme) == true + && (string.Equals(uriScheme, "http", StringComparison.OrdinalIgnoreCase) + || string.Equals(uriScheme, "https", StringComparison.OrdinalIgnoreCase))) + { + var endpointString = $"{uriScheme}://{ep.Spec.Address}:{ep.Spec.Port}"; + + // For project look into launch profile to append launch url + if (resourceViewModel is ProjectViewModel projectViewModel + && applicationModel.TryGetProjectWithPath(projectViewModel.ProjectPath, out var project) + && project.GetEffectiveLaunchProfile() is LaunchProfile launchProfile + && launchProfile.LaunchUrl is string launchUrl) + { + endpointString += $"/{launchUrl}"; + } + + return endpointString; + } + + return string.Empty; + }) + .Where(e => !string.Equals(e, string.Empty, StringComparison.Ordinal))); + } + + protected static int? GetExpectedEndpointsCount(IEnumerable services, CustomResource resource) + { + var expectedCount = 0; + if (resource.Metadata.Annotations?.TryGetValue(CustomResource.ServiceProducerAnnotation, out var servicesProducedAnnotationJson) == true) + { + var serviceProducerAnnotations = JsonSerializer.Deserialize(servicesProducedAnnotationJson); + if (serviceProducerAnnotations is not null) + { + foreach (var serviceProducer in serviceProducerAnnotations) + { + var matchingService = services.SingleOrDefault(s => s.Metadata.Name == serviceProducer.ServiceName); + + if (matchingService is null) + { + // We don't have matching service so we cannot compute endpoint count completely + // So we return null indicating that it is unknown. + // Dashboard should show this as Starting + return null; + } + + if (matchingService.Metadata.Annotations?.TryGetValue(CustomResource.UriSchemeAnnotation, out var uriScheme) == true + && (string.Equals(uriScheme, "http", StringComparison.OrdinalIgnoreCase) + || string.Equals(uriScheme, "https", StringComparison.OrdinalIgnoreCase))) + { + expectedCount++; + } + } + } + } + + return expectedCount; + } + + protected static void FillEnvironmentVariables(List target, List effectiveSource, List? specSource) + { + foreach (var env in effectiveSource) + { + if (env.Name is not null) + { + target.Add(new() + { + Name = env.Name, + Value = env.Value, + FromSpec = specSource?.Any(e => string.Equals(e.Name, env.Name, StringComparison.Ordinal)) == true + }); + } + } + + target.Sort((v1, v2) => string.Compare(v1.Name, v2.Name)); + } + + private sealed class ViewModelGeneratingEnumerator : IAsyncEnumerator> + { + private readonly Dictionary _resourceMap = []; + private readonly Dictionary> _resourceAssociatedServicesMap = []; + private readonly Dictionary _servicesMap = []; + private readonly Dictionary _endpointsMap = []; + private readonly HashSet _resourcesWithTaskLaunched = []; + private readonly ConcurrentDictionary> _additionalEnvVarsMap = []; + + private readonly DistributedApplicationModel _applicationModel; + private readonly Func _filterResource; + private readonly Func, IEnumerable, TResource, List?, TViewModel> _convertToViewModel; + private readonly CancellationToken _cancellationToken; + + private readonly Channel<(WatchEventType, string, CustomResource?)> _channel; + private readonly Queue> _buffer = new(); + + public ViewModelGeneratingEnumerator( + KubernetesService kubernetesService, + DistributedApplicationModel applicationModel, + Func _filterResource, + Func, IEnumerable, TResource, List?, TViewModel> convertToViewModel, + CancellationToken cancellationToken) + { + _applicationModel = applicationModel; + this._filterResource = _filterResource; + _convertToViewModel = convertToViewModel; + _cancellationToken = cancellationToken; + Current = default!; + + _channel = Channel.CreateUnbounded<(WatchEventType, string, CustomResource?)>(); + + RunWatchTask(kubernetesService, cancellationToken); + RunWatchTask(kubernetesService, cancellationToken); + RunWatchTask(kubernetesService, cancellationToken); + } + + public ResourceChanged Current { get; private set; } + + public async ValueTask MoveNextAsync() + { + while (true) + { + if (_cancellationToken.IsCancellationRequested) + { + break; + } + + if (_buffer.Count > 0) + { + Current = _buffer.Dequeue(); + + return true; + } + + // Process change in any of the watches to compute new view model if any + var (watchEventType, name, resource) = await _channel.Reader.ReadAsync(_cancellationToken).ConfigureAwait(false); + var objectChangeType = ToObjectChangeType(watchEventType); + // When we don't get resource that means this is notification generated when receiving env vars from docker command + // So we inject the resource from last copy we have + resource ??= _resourceMap[name]; + switch (resource) + { + case TResource customResource + when _filterResource(customResource) && ProcessChange(_resourceMap, watchEventType, customResource): + + UpdateAssociatedServicesMap(watchEventType, customResource); + if (customResource is Container container) + { + if (!_additionalEnvVarsMap.TryGetValue(name, out var list) + && !_resourcesWithTaskLaunched.Contains(name) + && container.Status?.State is not null && container.Status.ContainerId is not null) + { + // Container is ready to be inspected + // This task when returns will generate a notification in channel + _ = Task.Run(() => ComputeEnvironmentVariablesFromDocker(container, _cancellationToken)); + _resourcesWithTaskLaunched.Add(name); + } + // For containers we always send list of env vars which we may have computed earlier from docker command + Current = ComputeResult(objectChangeType, customResource, list); + } + else + { + Current = ComputeResult(objectChangeType, customResource, null); + } + + return true; + + case Endpoint endpoint + when ProcessChange(_endpointsMap, watchEventType, endpoint): + + var matchingResource = _resourceMap.Values.FirstOrDefault( + e => endpoint.Metadata.OwnerReferences?.Any(or => or.Kind == e.Kind && or.Name == e.Metadata.Name) == true); + + if (matchingResource is not null) + { + Current = ComputeResult(ObjectChangeType.Modified, matchingResource, null); + + return true; + } + + break; + + case Service service + when ProcessChange(_servicesMap, watchEventType, service): + + if (service.Metadata.Annotations?.TryGetValue(CustomResource.UriSchemeAnnotation, out var uriScheme) == true + && (string.Equals(uriScheme, "http", StringComparison.OrdinalIgnoreCase) + || string.Equals(uriScheme, "https", StringComparison.OrdinalIgnoreCase))) + { + // We only re-compute the view model if the service can generate an endpoint + foreach (var kvp in _resourceAssociatedServicesMap.Where(e => e.Value.Contains(name))) + { + _buffer.Enqueue(ComputeResult(ObjectChangeType.Modified, _resourceMap[kvp.Key], null)); + } + } + + break; + } + } + + Current = default!; + return false; + } + + public ValueTask DisposeAsync() + { + _channel.Writer.Complete(); + + return ValueTask.CompletedTask; + } + + private async Task ComputeEnvironmentVariablesFromDocker(Container container, CancellationToken cancellationToken) + { + IAsyncDisposable? processDisposable = null; + try + { + Task task; + var outputStringBuilder = new StringBuilder(); + var spec = new ProcessSpec(FileUtil.FindFullPathFromPath("docker")) + { + Arguments = $"container inspect --format=\"{{{{json .Config.Env}}}}\" {container.Status!.ContainerId}", + OnOutputData = s => outputStringBuilder.Append(s), + KillEntireProcessTree = false, + ThrowOnNonZeroReturnCode = false + }; + + (task, processDisposable) = ProcessUtil.Run(spec); + + var exitCode = (await task.WaitAsync(TimeSpan.FromSeconds(30), cancellationToken).ConfigureAwait(false)).ExitCode; + if (exitCode == 0) + { + var jsonArray = JsonNode.Parse(outputStringBuilder.ToString())?.AsArray(); + if (jsonArray is not null) + { + var envVars = new List(); + foreach (var item in jsonArray) + { + if (item is not null) + { + var parts = item.ToString().Split('=', 2); + envVars.Add(new EnvVar { Name = parts[0], Value = parts[1] }); + } + } + + _additionalEnvVarsMap[container.Metadata.Name] = envVars; + await _channel.Writer.WriteAsync((WatchEventType.Modified, container.Metadata.Name, null), _cancellationToken).ConfigureAwait(false); + } + } + } + catch + { + // If we fail to retrieve env vars from container at any point, we just skip it. + if (processDisposable != null) + { + await processDisposable.DisposeAsync().ConfigureAwait(false); + } + } + } + + private void UpdateAssociatedServicesMap(WatchEventType watchEventType, CustomResource resource) + { + // We keep track of associated services for the resource + // So whenever we get the service we can figure out if the service can generate endpoint for the resource + if (watchEventType == WatchEventType.Deleted) + { + _resourceAssociatedServicesMap.Remove(resource.Metadata.Name); + } + else if (resource.Metadata.Annotations?.TryGetValue(CustomResource.ServiceProducerAnnotation, out var servicesProducedAnnotationJson) == true) + { + var serviceProducerAnnotations = JsonSerializer.Deserialize(servicesProducedAnnotationJson); + if (serviceProducerAnnotations is not null) + { + _resourceAssociatedServicesMap[resource.Metadata.Name] + = serviceProducerAnnotations.Select(e => e.ServiceName).ToList(); + } + } + } + + private ResourceChanged ComputeResult(ObjectChangeType objectChangeType, TResource resource, List? additionalEnvVars) + => new(objectChangeType, + _convertToViewModel(_applicationModel, _servicesMap.Values, _endpointsMap.Values, resource, additionalEnvVars)); + + private static bool ProcessChange(Dictionary map, WatchEventType watchEventType, T resource) + where T : CustomResource + { + switch (watchEventType) + { + case WatchEventType.Added: + map.Add(resource.Metadata.Name, resource); + break; + + case WatchEventType.Modified: + map[resource.Metadata.Name] = resource; + break; + + case WatchEventType.Deleted: + map.Remove(resource.Metadata.Name); + break; + + default: + return false; + } + + return true; + } + + private static ObjectChangeType ToObjectChangeType(WatchEventType watchEventType) + => watchEventType switch + { + WatchEventType.Added => ObjectChangeType.Added, + WatchEventType.Modified => ObjectChangeType.Modified, + WatchEventType.Deleted => ObjectChangeType.Deleted, + _ => ObjectChangeType.Other + }; + + private void RunWatchTask(KubernetesService kubernetesService, CancellationToken cancellationToken) + where T : CustomResource + { + _ = Task.Run(async () => + { + await foreach (var tuple in kubernetesService.WatchAsync(cancellationToken: cancellationToken)) + { + await _channel.Writer.WriteAsync((tuple.Item1, tuple.Item2.Metadata.Name, tuple.Item2), _cancellationToken).ConfigureAwait(false); + } + }, cancellationToken); + } + } + + private sealed class ViewModelWatchEnumerator : IAsyncEnumerator> + { + private readonly List> _existingChanges; + private readonly Channel> _listeningChannel; + private readonly CancellationToken _cancellationToken; + + private int _index; + + public ViewModelWatchEnumerator( + List> existingChanges, + Channel> listeningChannel, + CancellationToken cancellationToken) + { + _existingChanges = existingChanges; + _listeningChannel = listeningChannel; + _cancellationToken = cancellationToken; + Current = default!; + } + + public ResourceChanged Current { get; private set; } + + public async ValueTask MoveNextAsync() + { + if (!_cancellationToken.IsCancellationRequested) + { + // We return from existing changes first + // and then start listening on the channel + if (_index < _existingChanges.Count) + { + Current = _existingChanges[_index++]; + + return true; + } + + try + { + Current = await _listeningChannel.Reader.ReadAsync(_cancellationToken).ConfigureAwait(false); + + return true; + } + catch { } + } + + Current = default!; + return false; + } + + public ValueTask DisposeAsync() + { + _listeningChannel.Writer.Complete(); + + return ValueTask.CompletedTask; + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,672 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Sockets; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Dcp.Model; +using k8s; + +namespace Aspire.Hosting.Dcp; + +internal class AppResource +{ + public IResource ModelResource { get; private set; } + public CustomResource DcpResource { get; private set; } + public virtual List ServicesProduced { get; private set; } = new(); + public virtual List ServicesConsumed { get; private set; } = new(); + + public AppResource(IResource modelResource, CustomResource dcpResource) + { + this.ModelResource = modelResource; + this.DcpResource = dcpResource; + } +} + +internal sealed class ServiceAppResource : AppResource +{ + public Service Service => (Service)DcpResource; + public ServiceBindingAnnotation ServiceBindingAnnotation { get; private set; } + public ServiceProducerAnnotation DcpServiceProducerAnnotation { get; private set; } + + public override List ServicesProduced + { + get { throw new InvalidOperationException("Service resources do not produce any services"); } + } + public override List ServicesConsumed + { + get { throw new InvalidOperationException("Service resources do not consume any services"); } + } + + public ServiceAppResource(IResource modelResource, Service service, ServiceBindingAnnotation sba) : base(modelResource, service) + { + ServiceBindingAnnotation = sba; + DcpServiceProducerAnnotation = new(service.Metadata.Name); + } +} + +internal sealed class ApplicationExecutor(DistributedApplicationModel model, KubernetesService kubernetesService) +{ + private const string DebugSessionPortVar = "DEBUG_SESSION_PORT"; + + // These environment variables should never be inherited from app host; + // they only make sense if they come from a launch profile of a service project. + private static readonly string[] s_doNotInheritEnvironmentVars = + { + "ASPNETCORE_URLS", + "DOTNET_LAUNCH_PROFILE", + "ASPNETCORE_ENVIRONMENT", + "DOTNET_ENVIRONMENT" + }; + + private readonly DistributedApplicationModel _model = model; + private readonly List _appResources = new(); + + public async Task RunApplicationAsync(CancellationToken cancellationToken = default) + { + AspireEventSource.Instance.DcpModelCreationStart(); + try + { + PrepareServices(); + PrepareContainers(); + PrepareExecutables(); + + await CreateServicesAsync(cancellationToken).ConfigureAwait(false); + + await CreateContainersAndExecutablesAsync(cancellationToken).ConfigureAwait(false); + } + finally + { + AspireEventSource.Instance.DcpModelCreationStop(); + } + + } + + public async Task StopApplicationAsync(CancellationToken cancellationToken = default) + { + try + { + AspireEventSource.Instance.DcpModelCleanupStart(); + await DeleteResourcesAsync("project", cancellationToken).ConfigureAwait(false); + await DeleteResourcesAsync("project", cancellationToken).ConfigureAwait(false); + await DeleteResourcesAsync("container", cancellationToken).ConfigureAwait(false); + await DeleteResourcesAsync("service", cancellationToken).ConfigureAwait(false); + } + finally + { + AspireEventSource.Instance.DcpModelCleanupStop(); + _appResources.Clear(); + } + } + + private async Task CreateServicesAsync(CancellationToken cancellationToken = default) + { + try + { + AspireEventSource.Instance.DcpServicesCreationStart(); + + var needAddressAllocated = _appResources.OfType().Where(sr => !sr.Service.HasCompleteAddress).ToList(); + + await CreateResourcesAsync(cancellationToken).ConfigureAwait(false); + + if (needAddressAllocated.Count == 0) + { + // No need to wait for any updates to Service objects from the orchestrator. + return; + } + + // We do not specify the initial list version, so the watcher will give us all updates to Service objects. + IAsyncEnumerable<(WatchEventType, Service)> serviceChangeEnumerator = kubernetesService.WatchAsync(cancellationToken: cancellationToken); + await foreach (var (evt, updated) in serviceChangeEnumerator) + { + if (evt == WatchEventType.Bookmark) { continue; } // Bookmarks do not contain any data. + + var srvResource = needAddressAllocated.Where(sr => sr.Service.Metadata.Name == updated.Metadata.Name).FirstOrDefault(); + if (srvResource == null) { continue; } // This service most likely already has full address information, so it is not on needAddressAllocated list. + + if (updated.HasCompleteAddress) + { + srvResource.Service.ApplyAddressInfoFrom(updated); + needAddressAllocated.Remove(srvResource); + } + + if (needAddressAllocated.Count == 0) + { + return; // We are done + } + } + } + finally + { + AspireEventSource.Instance.DcpServicesCreationStop(); + } + } + + private async Task CreateContainersAndExecutablesAsync(CancellationToken cancellationToken) + { + var toCreate = _appResources.Where(r => r.DcpResource is Container || r.DcpResource is Executable || r.DcpResource is ExecutableReplicaSet); + AddAllocatedEndpointInfo(toCreate); + + await CreateContainersAsync(toCreate.Where(ar => ar.DcpResource is Container), cancellationToken).ConfigureAwait(false); + await CreateExecutablesAsync(toCreate.Where(ar => ar.DcpResource is Executable || ar.DcpResource is ExecutableReplicaSet), cancellationToken).ConfigureAwait(false); + } + + private static void AddAllocatedEndpointInfo(IEnumerable resources) + { + foreach (var appResource in resources) + { + foreach (var sp in appResource.ServicesProduced) + { + var svc = (Service)sp.DcpResource; + if (!svc.HasCompleteAddress) + { + // This should never happen; if it does, we have a bug without a workaround for th the user. + throw new InvalidDataException($"Service {svc.Metadata.Name} should have valid address at this point"); + } + + var a = new AllocatedEndpointAnnotation( + sp.ServiceBindingAnnotation.Name, + PortProtocol.ToProtocolType(svc.Spec.Protocol), + svc.AllocatedAddress!, + (int)svc.AllocatedPort!, + sp.ServiceBindingAnnotation.UriScheme + ); + + appResource.ModelResource.Annotations.Add(a); + } + } + } + + private void PrepareServices() + { + var serviceProducers = _model.Resources + .Select(r => (ModelResource: r, SBAnnotations: r.Annotations.OfType())) + .Where(sp => sp.SBAnnotations.Any()); + + // We need to ensure that Services have unique names (otherwise we cannot really distinguish between + // services produced by different resources). + List serviceNames = new(); + + void addServiceAppResource(Service svc, IResource producingResource, ServiceBindingAnnotation sba) + { + svc.Spec.Protocol = PortProtocol.FromProtocolType(sba.Protocol); + svc.Spec.AddressAllocationMode = AddressAllocationModes.IPv4Loopback; + svc.Annotate(CustomResource.UriSchemeAnnotation, sba.UriScheme); + + _appResources.Add(new ServiceAppResource(producingResource, svc, sba)); + } + + foreach (var sp in serviceProducers) + { + var sbAnnotations = sp.SBAnnotations.ToArray(); + var replicas = sp.ModelResource.GetReplicaCount(); + + foreach (var sba in sbAnnotations) + { + var candidateServiceName = sbAnnotations.Length == 1 ? + GetObjectNameForResource(sp.ModelResource) : GetObjectNameForResource(sp.ModelResource, sba.Name); + var uniqueServiceName = GenerateUniqueServiceName(serviceNames, candidateServiceName); + var svc = Service.Create(uniqueServiceName); + + if (replicas > 1) + { + // Treat the port specified in the ServiceBindingAnnotation as desired port for the whole service. + // Each replica receives its own port. + svc.Spec.Port = sba.Port; + } + + addServiceAppResource(svc, sp.ModelResource, sba); + } + } + } + + private void PrepareExecutables() + { + PrepareProjectExecutables(); + PreparePlainExecutables(); + } + + private void PreparePlainExecutables() + { + var modelExecutableResources = _model.GetExecutableResources(); + + foreach (var executable in modelExecutableResources) + { + var exeName = GetObjectNameForResource(executable); + var exePath = executable.Command; + var exe = Executable.Create(exeName, exePath); + + exe.Spec.WorkingDirectory = executable.WorkingDirectory; + exe.Spec.Args = executable.Args?.ToList(); + exe.Spec.ExecutionType = ExecutionType.Process; + + var exeAppResource = new AppResource(executable, exe); + AddServicesProducedInfo(executable, exe, exeAppResource); + _appResources.Add(exeAppResource); + } + } + + private void PrepareProjectExecutables() + { + var modelProjectResources = _model.GetProjectResources(); + + foreach (var project in modelProjectResources) + { + if (!project.TryGetLastAnnotation(out var projectMetadata)) + { + throw new InvalidOperationException("A project resource is missing required metadata"); // Should never happen. + } + + CustomResource workload; + ExecutableSpec exeSpec; + IAnnotationHolder annotationHolder; + var workloadName = GetObjectNameForResource(project); + int replicas = project.GetReplicaCount(); + + if (replicas > 1) + { + var ers = ExecutableReplicaSet.Create(workloadName, replicas, "dotnet"); + exeSpec = ers.Spec.Template.Spec; + annotationHolder = ers.Spec.Template; + workload = ers; + } + else + { + var exe = Executable.Create(workloadName, "dotnet"); + exeSpec = exe.Spec; + annotationHolder = workload = exe; + } + + exeSpec.WorkingDirectory = Path.GetDirectoryName(projectMetadata.ProjectPath); + + annotationHolder.Annotate(Executable.CSharpProjectPathAnnotation, projectMetadata.ProjectPath); + annotationHolder.Annotate(Executable.LaunchProfileNameAnnotation, project.SelectLaunchProfileName() ?? string.Empty); + + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DebugSessionPortVar))) + { + exeSpec.ExecutionType = ExecutionType.IDE; + } + else + { + exeSpec.ExecutionType = ExecutionType.Process; + if (Environment.GetEnvironmentVariable("DOTNET_WATCH") != "1") + { + exeSpec.Args = [ + "run", + "--no-build", + "--project", + projectMetadata.ProjectPath, + ]; + } + else + { + exeSpec.Args = [ + "watch", + "--non-interactive", + "--no-hot-reload", + "--project", + projectMetadata.ProjectPath + ]; + } + + // We pretty much always want to suppress the normal launch profile handling + // because the settings from the profile will override the ambient environment settings, which is not what we want + // (the ambient environment settings for service processes come from the application model + // and should be HIGHER priority than the launch profile settings). + // This means we need to apply the launch profile settings manually--the invocation parameters here, + // and the environment variables/application URLs inside CreateExecutableAsync(). + exeSpec.Args.Add("--no-launch-profile"); + + string? launchProfileName = project.SelectLaunchProfileName(); + if (!string.IsNullOrEmpty(launchProfileName)) + { + var launchProfile = project.GetEffectiveLaunchProfile(); + if (launchProfile is not null && !string.IsNullOrWhiteSpace(launchProfile.CommandLineArgs)) + { + var cmdArgs = launchProfile.CommandLineArgs.Split((string?)null, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + if (cmdArgs is not null && cmdArgs.Length > 0) + { + exeSpec.Args.Add("--"); + exeSpec.Args.AddRange(cmdArgs); + } + } + } + } + + var exeAppResource = new AppResource(project, workload); + AddServicesProducedInfo(project, annotationHolder, exeAppResource); + _appResources.Add(exeAppResource); + } + } + + private async Task CreateExecutablesAsync(IEnumerable executableResources, CancellationToken cancellationToken) + { + try + { + AspireEventSource.Instance.DcpExecutablesCreateStart(); + + foreach (var er in executableResources) + { + ExecutableSpec spec; + Func> createResource; + + switch (er.DcpResource) + { + case Executable exe: + spec = exe.Spec; + createResource = async () => await kubernetesService.CreateAsync(exe, cancellationToken).ConfigureAwait(false); + break; + case ExecutableReplicaSet ers: + spec = ers.Spec.Template.Spec; + createResource = async () => await kubernetesService.CreateAsync(ers, cancellationToken).ConfigureAwait(false); + break; + default: + throw new InvalidOperationException($"Expected an Executable-like resource, but got {er.DcpResource.Kind} instead"); + } + + spec.Args ??= new(); + + if (er.ModelResource.TryGetAnnotationsOfType(out var exeArgsCallbacks)) + { + foreach (var exeArgsCallback in exeArgsCallbacks) + { + exeArgsCallback.Callback(spec.Args); + } + } + + var config = new Dictionary(); + var context = new EnvironmentCallbackContext("dcp", config); + + // Need to apply configuration settings manually; see PrepareExecutables() for details. + if (er.ModelResource is ProjectResource project && project.SelectLaunchProfileName() is { } launchProfileName && project.GetLaunchSettings() is { } launchSettings) + { + ApplyLaunchProfile(er, config, launchProfileName, launchSettings); + } + else + { + // If there is no launch profile, we want to make sure that certain environment variables are NOT inherited + foreach (var envVar in s_doNotInheritEnvironmentVars) + { + config.Add(envVar, ""); + } + } + + if (er.ModelResource.TryGetEnvironmentVariables(out var envVarAnnotations)) + { + foreach (var ann in envVarAnnotations) + { + ann.Callback(context); + } + } + + spec.Env = new(); + foreach (var c in config) + { + spec.Env.Add(new EnvVar { Name = c.Key, Value = c.Value }); + } + + var createdExecutable = await createResource().ConfigureAwait(false); + var dcpResourceAnnotation = new DcpResourceAnnotation(createdExecutable.Metadata.NamespaceProperty, createdExecutable.Metadata.Name, createdExecutable.Kind); + er.ModelResource.Annotations.Add(dcpResourceAnnotation); + } + + } + finally + { + AspireEventSource.Instance.DcpExecutablesCreateStop(); + } + } + + private static void ApplyLaunchProfile(AppResource executableResource, Dictionary config, string launchProfileName, LaunchSettings launchSettings) + { + // Populate DOTNET_LAUNCH_PROFILE environment variable for consistency with "dotnet run" and "dotnet watch". + config.Add("DOTNET_LAUNCH_PROFILE", launchProfileName); + + var launchProfile = launchSettings.Profiles[launchProfileName]; + if (!string.IsNullOrWhiteSpace(launchProfile.ApplicationUrl)) + { + int replicas = executableResource.ModelResource.GetReplicaCount(); + + if (replicas > 1) + { + // Can't use the information in ASPNETCORE_URLS directly when multiple replicas are in play. + // Instead we are going to SYNTHESIZE the new ASPNETCORE_URLS value based on the information about services produced by this resource. + var urls = executableResource.ServicesProduced.Select(sar => + { + var url = sar.ServiceBindingAnnotation.UriScheme + "://localhost:{{- portForServing \"" + sar.Service.Metadata.Name + "\" -}}"; + return url; + }); + config.Add("ASPNETCORE_URLS", string.Join(";", urls)); + } + else + { + config.Add("ASPNETCORE_URLS", launchProfile.ApplicationUrl); + } + } + + foreach (var envVar in launchProfile.EnvironmentVariables) + { + string value = Environment.ExpandEnvironmentVariables(envVar.Value); + config[envVar.Key] = value; + } + } + + private void PrepareContainers() + { + var modelContainerResources = _model.GetContainerResources(); + + foreach (var container in modelContainerResources) + { + if (!container.TryGetContainerImageName(out var containerImageName)) + { + // This should never happen! In order to get into this loop we need + // to have the annotation, if we don't have the annotation by the time + // we get here someone is doing something wrong. + throw new InvalidOperationException(); + } + + var ctr = Container.Create(container.Name, containerImageName); + + if (container.TryGetVolumeMounts(out var volumeMounts)) + { + ctr.Spec.VolumeMounts = new(); + + foreach (var mount in volumeMounts) + { + bool isBound = mount.Type == ApplicationModel.VolumeMountType.Bind; + var volumeSpec = new VolumeMount() + { + Source = isBound ? Path.GetFullPath(mount.Source) : mount.Source, + Target = mount.Target, + Type = isBound ? Model.VolumeMountType.Bind : Model.VolumeMountType.Named, + IsReadOnly = mount.IsReadOnly + }; + ctr.Spec.VolumeMounts.Add(volumeSpec); + } + } + + var containerAppResource = new AppResource(container, ctr); + AddServicesProducedInfo(container, ctr, containerAppResource); + _appResources.Add(containerAppResource); + } + } + + private async Task CreateContainersAsync(IEnumerable containerResources, CancellationToken cancellationToken) + { + try + { + AspireEventSource.Instance.DcpContainersCreateStart(); + + foreach (var cr in containerResources) + { + var dcpContainerResource = (Container)cr.DcpResource; + var modelContainerResource = cr.ModelResource; + + dcpContainerResource.Spec.Env = new(); + + if (modelContainerResource.TryGetEnvironmentVariables(out var containerEnvironmentVariables)) + { + var config = new Dictionary(); + var context = new EnvironmentCallbackContext("dcp", config); + + foreach (var v in containerEnvironmentVariables) + { + v.Callback(context); + } + + foreach (var kvp in config) + { + dcpContainerResource.Spec.Env.Add(new EnvVar { Name = kvp.Key, Value = kvp.Value }); + } + } + + if (cr.ServicesProduced.Count > 0) + { + dcpContainerResource.Spec.Ports = new(); + + foreach (var sp in cr.ServicesProduced) + { + var portSpec = new ContainerPortSpec() + { + ContainerPort = sp.DcpServiceProducerAnnotation.Port, + }; + + if (!string.IsNullOrEmpty(sp.DcpServiceProducerAnnotation.Address)) + { + portSpec.HostIP = sp.DcpServiceProducerAnnotation.Address; + } + + if (sp.ServiceBindingAnnotation.Port is not null) + { + portSpec.HostPort = sp.ServiceBindingAnnotation.Port; + } + + switch (sp.ServiceBindingAnnotation.Protocol) + { + case ProtocolType.Tcp: + portSpec.Protocol = PortProtocol.TCP; break; + case ProtocolType.Udp: + portSpec.Protocol = PortProtocol.UDP; break; + } + + dcpContainerResource.Spec.Ports.Add(portSpec); + } + } + + var createdContainer = await kubernetesService.CreateAsync(dcpContainerResource, cancellationToken).ConfigureAwait(false); + var dcpResourceAnnotation = new DcpResourceAnnotation(createdContainer.Metadata.NamespaceProperty, createdContainer.Metadata.Name, createdContainer.Kind); + cr.ModelResource.Annotations.Add(dcpResourceAnnotation); + } + } + finally + { + AspireEventSource.Instance.DcpContainersCreateStop(); + } + } + + private void AddServicesProducedInfo(IResource modelResource, IAnnotationHolder dcpResource, AppResource appResource) + { + string modelResourceName = "(unknown)"; + try + { + modelResourceName = GetObjectNameForResource(modelResource); + } + catch { } // For error messages only, OK to fall back to (unknown) + + var servicesProduced = _appResources.OfType().Where(r => r.ModelResource == modelResource); + foreach (var sp in servicesProduced) + { + if (modelResource.IsContainer()) + { + if (sp.ServiceBindingAnnotation.ContainerPort is null) + { + throw new InvalidOperationException($"The ServiceBindingAnnotation for container resource {modelResourceName} must specify the ContainerPort"); + } + + sp.DcpServiceProducerAnnotation.Port = sp.ServiceBindingAnnotation.ContainerPort; + } + else if (modelResource is ExecutableResource) + { + sp.DcpServiceProducerAnnotation.Port = sp.ServiceBindingAnnotation.Port; + } + else + { + if (sp.ServiceBindingAnnotation.Port is null) + { + throw new InvalidOperationException($"The ServiceBindingAnnotation for resource {modelResourceName} must specify the Port"); + } + + if (modelResource.GetReplicaCount() == 1) + { + // If multiple replicas are used, each replica will get its own port. + sp.DcpServiceProducerAnnotation.Port = sp.ServiceBindingAnnotation.Port; + } + } + + dcpResource.AnnotateAsObjectList(CustomResource.ServiceProducerAnnotation, sp.DcpServiceProducerAnnotation); + appResource.ServicesProduced.Add(sp); + } + } + + private async Task CreateResourcesAsync(CancellationToken cancellationToken) where RT : CustomResource + { + var resourcesToCreate = _appResources.Select(r => r.DcpResource).OfType(); + if (!resourcesToCreate.Any()) + { + return; + } + + // CONSIDER batched creation + foreach (var res in resourcesToCreate) + { + await kubernetesService.CreateAsync(res, cancellationToken).ConfigureAwait(false); + } + } + + private async Task DeleteResourcesAsync(string resourceName, CancellationToken cancellationToken) where RT : CustomResource + { + var resourcesToDelete = _appResources.Select(r => r.DcpResource).OfType(); + if (!resourcesToDelete.Any()) + { + return; + } + + foreach (var res in resourcesToDelete) + { + try + { + await kubernetesService.DeleteAsync(res.Metadata.Name, res.Metadata.NamespaceProperty, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + Console.WriteLine($"Could not stop {resourceName} '{res.Metadata.Name}': {ex}"); + } + } + } + + private static string GetObjectNameForResource(IResource resource, string suffix = "") + { + string maybeWithSuffix(string s) => string.IsNullOrWhiteSpace(suffix) ? s : $"{s}_{suffix}"; + return maybeWithSuffix(resource.Name); + } + + private static string GenerateUniqueServiceName(List serviceNames, string candidateName) + { + int suffix = 1; + string uniqueName = candidateName; + + while (serviceNames.Contains(uniqueName)) + { + uniqueName = $"{candidateName}_{suffix}"; + suffix++; + if (suffix == 100) + { + // Should never happen, but we do not want to ever get into a infinite loop situation either. + throw new ArgumentException($"Could not generate a unique name for service '{candidateName}'"); + } + } + + serviceNames.Add(uniqueName); + return uniqueName; + } + +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/DcpDistributedApplicationLifecycleHook.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/DcpDistributedApplicationLifecycleHook.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/DcpDistributedApplicationLifecycleHook.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/DcpDistributedApplicationLifecycleHook.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Lifecycle; +using Aspire.Hosting.Publishing; +using Microsoft.Extensions.Options; +using System.Net.Sockets; + +namespace Aspire.Hosting.Dcp; + +public sealed class DcpDistributedApplicationLifecycleHook(IOptions publishingOptions) : IDistributedApplicationLifecycleHook +{ + private readonly IOptions _publishingOptions = publishingOptions; + + public Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default) + { + var publisher = _publishingOptions.Value?.Publisher == null ? "dcp" : _publishingOptions.Value.Publisher; + + if (publisher == "dcp") + { + PrepareServices(appModel); + } + + return Task.CompletedTask; + } + + private void PrepareServices(DistributedApplicationModel model) + { + // Automatically add ServiceBindingAnnotations to project resources based on ApplicationUrl set in the launch profile. + foreach (var projectResource in model.Resources.OfType()) + { + var selectedLaunchProfileName = projectResource.SelectLaunchProfileName(); + if (selectedLaunchProfileName is null) + { + continue; + } + + var launchProfile = projectResource.GetEffectiveLaunchProfile(); + if (launchProfile is null) + { + continue; + } + + var urlsFromApplicationUrl = launchProfile.ApplicationUrl?.Split(';') ?? []; + foreach (var url in urlsFromApplicationUrl) + { + var uri = new Uri(url); + + if (projectResource.Annotations.OfType().Any(sb => sb.Name == uri.Scheme)) + { + // If someone uses WithServiceBinding in the dev host to register a service binding with the name + // http or https this exception will be thrown. + throw new DistributedApplicationException($"Service binding annotation with name '{uri.Scheme}' already exists."); + } + + var generatedServiceBindingAnnotation = new ServiceBindingAnnotation( + ProtocolType.Tcp, + uriScheme: uri.Scheme, + port: uri.Port + ); + + projectResource.Annotations.Add(generatedServiceBindingAnnotation); + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/DcpHostService.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/DcpHostService.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/DcpHostService.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/DcpHostService.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,412 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Collections; +using System.Diagnostics; +using System.Globalization; +using System.IO.Pipelines; +using System.Net.Sockets; +using System.Text; +using Aspire.Dashboard; +using Aspire.Dashboard.Model; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Dashboard; +using Aspire.Hosting.Dcp.Process; +using Aspire.Hosting.Properties; +using Aspire.Hosting.Publishing; +using Aspire.Hosting.Utils; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Aspire.Hosting.Dcp; + +internal sealed class DcpHostService : IHostedLifecycleService, IAsyncDisposable +{ + private const int LoggingSocketConnectionBacklog = 3; + private readonly ApplicationExecutor _appExecutor; + private readonly DistributedApplicationModel _applicationModel; + private IAsyncDisposable? _dcpRunDisposable; + private readonly ILoggerFactory _loggerFactory; + private readonly ILogger _logger; + private readonly DashboardWebApplication? _dashboard; + private readonly DcpOptions _dcpOptions; + private readonly PublishingOptions _publishingOptions; + private readonly Locations _locations; + + public DcpHostService(DistributedApplicationModel applicationModel, + DistributedApplicationOptions options, + ILoggerFactory loggerFactory, + IOptions dcpOptions, + IOptions publishingOptions, + ApplicationExecutor appExecutor, + Locations locations, + KubernetesService kubernetesService) + { + _applicationModel = applicationModel; + _loggerFactory = loggerFactory; + _logger = loggerFactory.CreateLogger(); + _dcpOptions = dcpOptions.Value; + _publishingOptions = publishingOptions.Value; + _appExecutor = appExecutor; + _locations = locations; + + if (options.DashboardEnabled) + { + _dashboard = new DashboardWebApplication(serviceCollection => + { + serviceCollection.AddSingleton(_applicationModel); + serviceCollection.AddSingleton(kubernetesService); + serviceCollection.AddScoped(); + }); + } + } + + public async Task StartAsync(CancellationToken cancellationToken = default) + { + if (_publishingOptions.Publisher is not null && _publishingOptions.Publisher != "dcp") + { + return; + } + + EnsureDockerIfNecessary(); + EnsureDcpHostRunning(); + await _appExecutor.RunApplicationAsync(cancellationToken).ConfigureAwait(false); + + if (_dashboard is not null) + { + await _dashboard.StartAsync(cancellationToken).ConfigureAwait(false); + } + } + + public async Task StopAsync(CancellationToken cancellationToken = default) + { + if (_publishingOptions.Publisher != "dcp" || _publishingOptions.Publisher is not null) + { + return; + } + + await _appExecutor.StopApplicationAsync(cancellationToken).ConfigureAwait(false); + + // Stop the dashboard after the application has stopped + if (_dashboard is not null) + { + await _dashboard.StopAsync(cancellationToken).ConfigureAwait(false); + } + } + + public async ValueTask DisposeAsync() + { + if (_dcpRunDisposable is null) + { + return; + } + + await _appExecutor.StopApplicationAsync().ConfigureAwait(false); + await _dcpRunDisposable.DisposeAsync().ConfigureAwait(false); + _dcpRunDisposable = null; + } + + private void EnsureDcpHostRunning() + { + AspireEventSource.Instance.DcpApiServerLaunchStart(); + + try + { + var dcpProcessSpec = CreateDcpProcessSpec(_locations); + + // Enable Unix Domain Socket based log streaming from DCP + try + { + AspireEventSource.Instance.DcpLogSocketCreateStart(); + Socket loggingSocket = CreateLoggingSocket(_locations.DcpLogSocket); + loggingSocket.Listen(LoggingSocketConnectionBacklog); + + dcpProcessSpec.EnvironmentVariables.Add("DCP_LOG_SOCKET", _locations.DcpLogSocket); + + _ = Task.Run(() => StartLoggingSocketAsync(loggingSocket), CancellationToken.None); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Failed to enable orchestration logging: {ex}"); + } + finally + { + AspireEventSource.Instance.DcpLogSocketCreateStop(); + } + + (_, _dcpRunDisposable) = ProcessUtil.Run(dcpProcessSpec); + } + finally + { + AspireEventSource.Instance.DcpApiServerLaunchStop(); + } + + } + + private ProcessSpec CreateDcpProcessSpec(Locations locations) + { + string? dcpExePath = _dcpOptions.CliPath; + if (!File.Exists(dcpExePath)) + { + throw new FileNotFoundException($"The Aspire application host is not installed at \"{dcpExePath}\". The application cannot be run without it.", dcpExePath); + } + + ProcessSpec dcpProcessSpec = new ProcessSpec(dcpExePath) + { + WorkingDirectory = Directory.GetCurrentDirectory(), + Arguments = $"start-apiserver --monitor {Environment.ProcessId} --detach --kubeconfig \"{locations.DcpKubeconfigPath}\"", + OnOutputData = Console.Out.Write, + OnErrorData = Console.Error.Write, + }; + + _logger.LogInformation("Starting DCP with arguments: {Arguments}", dcpProcessSpec.Arguments); + + foreach (DictionaryEntry de in Environment.GetEnvironmentVariables()) + { + var key = de.Key?.ToString(); + var val = de.Value?.ToString(); + if (key is not null && val is not null) + { + dcpProcessSpec.EnvironmentVariables.Add(key, val); + } + } + + if (!string.IsNullOrEmpty(_dcpOptions.ExtensionsPath)) + { + dcpProcessSpec.EnvironmentVariables.Add("DCP_EXTENSIONS_PATH", _dcpOptions.ExtensionsPath); + } + + if (!string.IsNullOrEmpty(_dcpOptions.BinPath)) + { + dcpProcessSpec.EnvironmentVariables.Add("DCP_BIN_PATH", _dcpOptions.BinPath); + } + + // Set an environment variable to contain session info that should be deleted when DCP is done + // Currently this contains the Unix socket for logging and the kubeconfig + dcpProcessSpec.EnvironmentVariables.Add("DCP_SESSION_FOLDER", locations.DcpSessionDir); + return dcpProcessSpec; + } + + // Docker goes to into resource saver mode after 5 minutes of not running a container (by default). + // While in this mode, the commands we use for the docker check take quite some time + private const int WaitTimeForDockerTestCommandInSeconds = 25; + + private void EnsureDockerIfNecessary() + { + // If we don't have any respirces that need a container then we + // don't need to check for Docker. + if (!_applicationModel.Resources.Any(c => c.Annotations.OfType().Any())) + { + return; + } + + AspireEventSource.Instance.DockerHealthCheckStart(); + + try + { + var dockerCommandArgs = "ps --latest --quiet"; + var dockerStartInfo = new ProcessStartInfo() + { + FileName = FileUtil.FindFullPathFromPath("docker"), + Arguments = dockerCommandArgs, + UseShellExecute = true, + WindowStyle = ProcessWindowStyle.Hidden + }; + var process = System.Diagnostics.Process.Start(dockerStartInfo); + if (process is { } && process.WaitForExit(TimeSpan.FromSeconds(WaitTimeForDockerTestCommandInSeconds))) + { + if (process.ExitCode != 0) + { + Console.Error.WriteLine(string.Format( + CultureInfo.InvariantCulture, + Resources.DockerUnhealthyExceptionMessage, + $"docker {dockerCommandArgs}", + process.ExitCode + )); + Environment.Exit((int)DockerHealthCheckFailures.Unhealthy); + } + } + else + { + Console.Error.WriteLine(string.Format( + CultureInfo.InvariantCulture, + Resources.DockerUnresponsiveExceptionMessage, + $"docker {dockerCommandArgs}", + WaitTimeForDockerTestCommandInSeconds + )); + Environment.Exit((int)DockerHealthCheckFailures.Unresponsive); + } + + // If we get to here all is good! + + } + catch (Exception ex) when (ex is not DistributedApplicationException) + { + Console.Error.WriteLine(string.Format( + CultureInfo.InvariantCulture, + Resources.DockerPrerequisiteMissingExceptionMessage, + ex.ToString() + )); + Environment.Exit((int)DockerHealthCheckFailures.PrerequisiteMissing); + } + finally + { + AspireEventSource.Instance?.DockerHealthCheckStop(); + } + } + + private static Socket CreateLoggingSocket(string socketPath) + { + string? directoryName = Path.GetDirectoryName(socketPath); + if (!string.IsNullOrEmpty(directoryName)) + { + Directory.CreateDirectory(directoryName); + } + + Socket socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + socket.Bind(new UnixDomainSocketEndPoint(socketPath)); + + return socket; + } + + private async Task StartLoggingSocketAsync(Socket socket) + { + while (true) + { + try + { + Socket acceptedSocket = await socket.AcceptAsync().ConfigureAwait(false); + _ = Task.Run(() => LogSocketOutputAsync(acceptedSocket), CancellationToken.None); + } + catch + { + // Suppress exceptions reading logs from DCP controllers + } + } + } + + private async Task LogSocketOutputAsync(Socket socket) + { + var reader = PipeReader.Create(new NetworkStream(socket)); + + // Logger cache to avoid creating a new string per log line, for a few categories + var loggerCache = new Dictionary(); + + (ILogger, LogLevel, string message) GetLogInfo(ReadOnlySpan line) + { + // The log format is + // \t\t\t + // e.g. 2023-09-19T20:40:50.509-0700 info dcpctrl.ServiceReconciler service /apigateway is now in state Ready {"ServiceName": {"name":"apigateway"}} + + var tab = line.IndexOf((byte)'\t'); + var date = line[..tab]; + line = line[(tab + 1)..]; + tab = line.IndexOf((byte)'\t'); + var level = line[..tab]; + line = line[(tab + 1)..]; + tab = line.IndexOf((byte)'\t'); + var category = line[..tab]; + line = line[(tab + 1)..]; + var message = line; + + var logLevel = LogLevel.Information; + + if (level.SequenceEqual("info"u8)) + { + logLevel = LogLevel.Information; + } + else if (level.SequenceEqual("error"u8)) + { + logLevel = LogLevel.Error; + } + else if (level.SequenceEqual("warning"u8)) + { + logLevel = LogLevel.Warning; + } + else if (level.SequenceEqual("debug"u8)) + { + logLevel = LogLevel.Debug; + } + else if (level.SequenceEqual("trace"u8)) + { + logLevel = LogLevel.Trace; + } + + var hash = new HashCode(); + hash.AddBytes(category); + var hashValue = hash.ToHashCode(); + + if (!loggerCache.TryGetValue(hashValue, out var logger)) + { + // loggerFactory.CreateLogger internally caches, but we may as well cache the logger as well as the string + // for the lifetime of this socket + loggerCache[hashValue] = logger = _loggerFactory.CreateLogger(Encoding.UTF8.GetString(category)); + } + + return (logger, logLevel, Encoding.UTF8.GetString(message)); + } + + try + { + void LogLines(in ReadOnlySequence buffer, out SequencePosition position) + { + var seq = new SequenceReader(buffer); + while (seq.TryReadTo(out ReadOnlySpan line, (byte)'\n')) + { + var (logger, logLevel, message) = GetLogInfo(line); + + logger.Log(logLevel, 0, message, null, static (value, ex) => value); + } + + position = seq.Position; + } + + while (true) + { + var result = await reader.ReadAsync().ConfigureAwait(false); + + if (result.IsCompleted) + { + break; + } + + LogLines(result.Buffer, out var position); + + reader.AdvanceTo(position); + } + } + catch + { + // Suppress exceptions reading logs from DCP controllers + } + finally + { + reader.Complete(); + } + } + + public Task StartedAsync(CancellationToken _) + { + AspireEventSource.Instance.DcpHostStartupStop(); + return Task.CompletedTask; + } + + public Task StartingAsync(CancellationToken cancellationToken) + { + AspireEventSource.Instance.DcpHostStartupStart(); + return Task.CompletedTask; + } + + public Task StoppedAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task StoppingAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/DcpOptions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/DcpOptions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/DcpOptions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/DcpOptions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection; +using Aspire.Hosting.Publishing; +using Microsoft.Extensions.Configuration; + +namespace Aspire.Hosting.Dcp; + +public sealed class DcpOptions +{ + private const string DcpCliPathMetadataKey = "DcpCliPath"; + private const string DcpExtensionsPathMetadataKey = "DcpExtensionsPath"; + private const string DcpBinPathMetadataKey = "DcpBinPath"; + + public static string DcpPublisher = nameof(DcpPublisher); + + /// + /// The path to the DCP executable used for Aspire orchestration + /// + /// + /// C:\Program Files\dotnet\packs\Aspire.Hosting.Orchestration.win-x64\8.0.0-preview.1.23518.6\tools\dcp.exe + /// + public string? CliPath { get; set; } + + /// + /// Optional path to a folder container the DCP extension assemblies (dcpd, dcpctrl, etc.) + /// + /// + /// C:\Program Files\dotnet\packs\Aspire.Hosting.Orchestration.win-x64\8.0.0-preview.1.23518.6\tools\ext\ + /// + public string? ExtensionsPath { get; set; } + + /// + /// Optional path to a folder containing additional DCP binaries (traefik, etc.) + /// + /// + /// C:\Program Files\dotnet\packs\Aspire.Hosting.Orchestration.win-x64\8.0.0-preview.1.23518.6\tools\ext\bin\ + /// + public string? BinPath { get; set; } + + public void ApplyApplicationConfiguration(DistributedApplicationOptions appOptions, IConfiguration dcpPublisherConfiguration, IConfiguration publishingConfiguration) + { + string? publisher = publishingConfiguration[nameof(PublishingOptions.Publisher)]; + if (publisher is not null && publisher != "dcp") + { + // If DCP is not set as the publisher, don't calculate the DCP config + return; + } + + if (!string.IsNullOrEmpty(dcpPublisherConfiguration[nameof(CliPath)])) + { + // If an explicit path to DCP was provided from configuration, don't try to resolve via assembly attributes + CliPath = dcpPublisherConfiguration[nameof(CliPath)]; + } + else + { + // Calculate DCP locations from configuration options + var appHostAssembly = Assembly.GetEntryAssembly(); + if (!string.IsNullOrEmpty(appOptions.AssemblyName)) + { + try + { + // Find an assembly in the current AppDomain with the given name + appHostAssembly = Assembly.Load(appOptions.AssemblyName); + if (appHostAssembly == null) + { + throw new FileNotFoundException("No assembly with name '{appOptions.AssemblyName}' exists in the current AppDomain."); + } + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to load AppHost assembly '{appOptions.AssemblyName}' specified in {nameof(DistributedApplicationOptions)}.", ex); + } + } + + + var assemblyMetadata = appHostAssembly?.GetCustomAttributes(); + CliPath = GetMetadataValue(assemblyMetadata, DcpCliPathMetadataKey); + ExtensionsPath = GetMetadataValue(assemblyMetadata, DcpExtensionsPathMetadataKey); + BinPath = GetMetadataValue(assemblyMetadata, DcpBinPathMetadataKey); + } + + if (string.IsNullOrEmpty(CliPath)) + { + throw new InvalidOperationException($"Could not resolve the path to the Aspire application host. The application cannot be run without it."); + } + } + + private static string? GetMetadataValue(IEnumerable? assemblyMetadata, string key) + { + return assemblyMetadata?.FirstOrDefault(m => string.Equals(m.Key, key, StringComparison.OrdinalIgnoreCase))?.Value; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/KubernetesService.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/KubernetesService.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/KubernetesService.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/KubernetesService.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,235 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using Aspire.Hosting.Dcp.Model; +using k8s; +using k8s.Exceptions; +using k8s.Models; + +namespace Aspire.Hosting.Dcp; + +public enum DcpApiOperationType +{ + Create = 1, + List = 2, + Delete = 3, + Watch = 4 +} + +internal sealed class KubernetesService(Locations locations) : IDisposable +{ + private static readonly TimeSpan s_initialRetryDelay = TimeSpan.FromMilliseconds(100); + private static GroupVersion GroupVersion => Model.Dcp.GroupVersion; + + private IKubernetes? _kubernetes; + private TimeSpan _maxRetryDuration = TimeSpan.FromSeconds(5); + + public TimeSpan MaxRetryDuration + { + get + { + return _maxRetryDuration; + } + set + { + _maxRetryDuration = value; + } + } + + public Task CreateAsync(T obj, CancellationToken cancellationToken = default) + where T : CustomResource + { + var resourceType = GetResourceFor(); + var namespaceParameter = obj.Namespace(); + + return ExecuteWithRetry( + DcpApiOperationType.Create, + resourceType, + async (kubernetes) => + { + var response = string.IsNullOrEmpty(namespaceParameter) + ? await kubernetes.CustomObjects.CreateClusterCustomObjectWithHttpMessagesAsync( + obj, + GroupVersion.Group, + GroupVersion.Version, + resourceType, + cancellationToken: cancellationToken).ConfigureAwait(false) + : await kubernetes.CustomObjects.CreateNamespacedCustomObjectWithHttpMessagesAsync( + obj, + GroupVersion.Group, + GroupVersion.Version, + namespaceParameter, + resourceType, + cancellationToken: cancellationToken).ConfigureAwait(false); + + return KubernetesJson.Deserialize(response.Body.ToString()); + }, + cancellationToken); + } + + public Task> ListAsync(string? namespaceParameter = null, CancellationToken cancellationToken = default) + where T : CustomResource + { + var resourceType = GetResourceFor(); + + return ExecuteWithRetry( + DcpApiOperationType.List, + resourceType, + async (kubernetes) => + { + var response = string.IsNullOrEmpty(namespaceParameter) + ? await kubernetes.CustomObjects.ListClusterCustomObjectWithHttpMessagesAsync( + GroupVersion.Group, + GroupVersion.Version, + resourceType, + cancellationToken: cancellationToken).ConfigureAwait(false) + : await kubernetes.CustomObjects.ListNamespacedCustomObjectWithHttpMessagesAsync( + GroupVersion.Group, + GroupVersion.Version, + namespaceParameter, + resourceType, + cancellationToken: cancellationToken).ConfigureAwait(false); + + return KubernetesJson.Deserialize>(response.Body.ToString()).Items; + }, + cancellationToken); + } + + public Task DeleteAsync(string name, string? namespaceParameter = null, CancellationToken cancellationToken = default) + where T : CustomResource + { + var resourceType = GetResourceFor(); + + return ExecuteWithRetry( + DcpApiOperationType.Delete, + resourceType, + async (kubernetes) => + { + var response = string.IsNullOrEmpty(namespaceParameter) + ? await kubernetes.CustomObjects.DeleteClusterCustomObjectWithHttpMessagesAsync( + GroupVersion.Group, + GroupVersion.Version, + resourceType, + name, + cancellationToken: cancellationToken).ConfigureAwait(false) + : await kubernetes.CustomObjects.DeleteNamespacedCustomObjectWithHttpMessagesAsync( + GroupVersion.Group, + GroupVersion.Version, + namespaceParameter, + resourceType, + name, + cancellationToken: cancellationToken).ConfigureAwait(false); + + return KubernetesJson.Deserialize(response.Body.ToString()); + }, + cancellationToken); + } + + public async IAsyncEnumerable<(WatchEventType, T)> WatchAsync( + string? namespaceParameter = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + where T : CustomResource + { + var resourceType = GetResourceFor(); + var result = await ExecuteWithRetry( + DcpApiOperationType.Watch, + resourceType, + (kubernetes) => + { + var responseTask = string.IsNullOrEmpty(namespaceParameter) + ? kubernetes.CustomObjects.ListClusterCustomObjectWithHttpMessagesAsync( + GroupVersion.Group, + GroupVersion.Version, + resourceType, + watch: true, + cancellationToken: cancellationToken) + : kubernetes.CustomObjects.ListNamespacedCustomObjectWithHttpMessagesAsync( + GroupVersion.Group, + GroupVersion.Version, + namespaceParameter, + resourceType, + watch: true, + cancellationToken: cancellationToken); + + return responseTask.WatchAsync(null, cancellationToken); + }, + cancellationToken).ConfigureAwait(false); + + await foreach (var item in result) + { + yield return item; + } + } + + public void Dispose() + { + _kubernetes?.Dispose(); + } + + private static string GetResourceFor() where T : CustomResource + { + if (!Model.Dcp.Schema.TryGet(out var kindWithResource)) + { + throw new InvalidOperationException($"Unknown custom resource type: {typeof(T).Name}"); + } + + return kindWithResource.Resource; + } + + private Task ExecuteWithRetry(DcpApiOperationType operationType, string resourceType, Func operation, CancellationToken cancellationToken) + { + return ExecuteWithRetry(operationType, resourceType, (IKubernetes kubernetes) => Task.FromResult(operation(kubernetes)), cancellationToken); + } + + private async Task ExecuteWithRetry(DcpApiOperationType operationType, string resourceType, Func> operation, CancellationToken cancellationToken) + { + var currentTimestamp = DateTime.UtcNow; + var delay = s_initialRetryDelay; + AspireEventSource.Instance.DcpApiCallStart(operationType, resourceType); + + try + { + while (true) + { + try + { + EnsureKubernetes(); + return await operation(_kubernetes!).ConfigureAwait(false); + } + catch (Exception e) when (IsRetryable(e)) + { + if (DateTime.UtcNow.Subtract(currentTimestamp) > MaxRetryDuration) + { + AspireEventSource.Instance.DcpApiCallTimeout(operationType, resourceType); + throw; + } + + await Task.Delay(delay, cancellationToken).ConfigureAwait(false); + delay *= 2; + AspireEventSource.Instance.DcpApiCallRetry(operationType, resourceType); + } + } + } + finally + { + AspireEventSource.Instance.DcpApiCallStop(operationType, resourceType); + } + + } + + private static bool IsRetryable(Exception ex) => ex is HttpRequestException || ex is KubeConfigException; + + private void EnsureKubernetes() + { + if (_kubernetes != null) { return; } + + lock (Model.Dcp.Schema) + { + if (_kubernetes != null) { return; } + + var config = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeconfigPath: locations.DcpKubeconfigPath, useRelativePaths: false); + _kubernetes = new Kubernetes(config); + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Locations.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Locations.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Locations.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Locations.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; + +namespace Aspire.Hosting.Dcp; + +internal sealed class Locations(string basePath) +{ + public string DcpTempDir => Path.Join(basePath, "aspire"); + + public string DcpSessionDir => Path.Combine(DcpTempDir, "session", Environment.ProcessId.ToString(CultureInfo.InvariantCulture)); + + public string DcpKubeconfigPath => Path.Combine(DcpSessionDir, "kubeconfig"); + + public string DcpLogSocket => Path.Combine(DcpSessionDir, "output.sock"); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/Container.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/Container.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/Container.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/Container.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,222 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Sockets; +using System.Text.Json.Serialization; +using k8s.Models; + +namespace Aspire.Hosting.Dcp.Model; + +public class ContainerSpec +{ + // Image to be used to create the container + [JsonPropertyName("image")] + public string? Image { get; set; } + + // Volumes that should be mounted into the container + [JsonPropertyName("volumeMounts")] + public List? VolumeMounts { get; set; } + + // Exposed ports + [JsonPropertyName("ports")] + public List? Ports { get; set; } + + // Environment variables to be used for the container + [JsonPropertyName("env")] + public List? Env { get; set; } + + // Environment files to use to populate Container environment during startup + [JsonPropertyName("envFiles")] + public List? EnvFiles { get; set; } + + // Container restart policy + [JsonPropertyName("restartPolicy")] + public string? RestartPolicy { get; set; } = ContainerRestartPolicy.None; + + // Command to run in the container (entrypoint) + [JsonPropertyName("command")] + public string? Command { get; set; } + + // Arguments to pass to the command that starts the container + [JsonPropertyName("args")] + public List? Args { get; set; } +} + +public static class VolumeMountType +{ + // A volume mount to a host directory + public static readonly string Bind = "bind"; + + // A volume mount to a named volume managed by the container orchestrator + public static readonly string Named = "volume"; +} + +public class VolumeMount +{ + [JsonPropertyName("type")] + public string Type { get; set; } = VolumeMountType.Bind; + + // Bind mounts: the host directory to mount + // Volume mounts: name of the volume to mount + [JsonPropertyName("source")] + public string? Source { get; set; } + + // The path within the container that the mount will use + [JsonPropertyName("target")] + public string? Target { get; set; } + + // True if the mounted file system is supposed to be read-only + [JsonPropertyName("readOnly")] + public bool IsReadOnly { get; set; } = false; +} + +public static class ContainerRestartPolicy +{ + // Do not automatically restart the container when it exits (default) + public static readonly string None = "no"; + + // Restart only if the container exits with non-zero status + public static readonly string OnFailure = "on-failure"; + + // Restart container, except if container is explicitly stopped (or container daemon is stopped/restarted) + public static readonly string UnlessStopped = "unless-stopped"; + + // Always try to restart the container + public static readonly string Always = "always"; +} + +public static class PortProtocol +{ + public const string TCP = "TCP"; + + public const string UDP = "UDP"; + + public static string Canonicalize(string protocol) + { + var protocolUC = protocol.ToUpperInvariant(); + switch (protocolUC) + { + case TCP: + case UDP: + return protocolUC; + default: + throw new ArgumentException("Port protocol value must be 'TCP' or 'UDP'"); + } + } + + public static ProtocolType ToProtocolType(string protocol) + { + var canonical = Canonicalize(protocol); + switch (canonical) + { + case TCP: + return ProtocolType.Tcp; + case UDP: + return ProtocolType.Udp; + default: + throw new ArgumentException("Supported protocols are TCP and UDP"); + } + } + + public static string FromProtocolType(ProtocolType protocolType) + { + switch (protocolType) + { + case ProtocolType.Tcp: + return TCP; + case ProtocolType.Udp: + return UDP; + default: + throw new ArgumentException("Supported protocols are TCP and UDP"); + } + } +} + +public class ContainerPortSpec +{ + // Optional: If specified, this must be a valid port number, 0 < x < 65536. + [JsonPropertyName("hostPort")] + public int? HostPort { get; set; } + + // Required: This must be a valid port number, 0 < x < 65536. + [JsonPropertyName("containerPort")] + public int? ContainerPort { get; set; } + + // The network protocol to be used, defaults to TCP + [JsonPropertyName("protocol")] + public string Protocol { get; set; } = PortProtocol.TCP; + + // Optional: What host IP to bind the external port to. + [JsonPropertyName("hostIP")] + public string? HostIP { get; set; } +} + +public class ContainerStatus : V1Status +{ + // Current state of the Container. + [JsonPropertyName("state")] + public string? State { get; set; } + + // ID of the Container (if an attempt to start the Container was made) + [JsonPropertyName("containerId")] + public string? ContainerId { get; set; } + + // Timestamp of the Container start attempt + [JsonPropertyName("startupTimestamp")] + public DateTimeOffset? StartupTimestamp { get; set; } + + // Timestamp when the Container was terminated last + [JsonPropertyName("finishTimestamp")] + public DateTimeOffset? FinishTimestamp { get; set; } + + // Exit code of the Container. + // Default is -1, meaning the exit code is not known, or the container is still running. + [JsonPropertyName("exitCode")] + public int ExitCode { get; set; } = Conventions.UnknownExitCode; + + // Note: the ContainerStatus has "Message" property that represents a human-readable information about Container state. + // It is provided by V1Status base class. +} + +public static class ContainerState +{ + // Pending is the initial Container state. No attempt has been made to run the container yet. + public static readonly string Pending = "Pending"; + + // A start attempt was made, but it failed + public static readonly string FailedToStart = "FailedToStart"; + + // Container has been started and is executing + public static readonly string Running = "Running"; + + // Container is paused + public static readonly string Paused = "Paused"; + + // Container finished execution + public static readonly string Exited = "Exited"; + + // Container was running at some point, but has been removed. + public static readonly string Removed = "Removed"; + + // Unknown means for some reason container state is unavailable. + public static readonly string Unknown = "Unknown"; +} + +public class Container : CustomResource +{ + [JsonConstructor] + public Container(ContainerSpec spec) : base(spec) { } + + public static Container Create(string name, string image) + { + var c = new Container(new ContainerSpec { Image = image }); + + c.Kind = Dcp.ContainerKind; + c.ApiVersion = Dcp.GroupVersion.ToString(); + c.Metadata.Name = name; + c.Metadata.NamespaceProperty = string.Empty; + + return c; + } +} + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/Endpoint.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/Endpoint.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/Endpoint.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/Endpoint.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization; +using k8s.Models; + +namespace Aspire.Hosting.Dcp.Model; + +public class EndpointSpec +{ + // Namespace of the service the endpoint implements + [JsonPropertyName("serviceNamespace")] + public string? ServiceNamespace { get; set; } + + // Name of the service the endpoint implements + [JsonPropertyName("serviceName")] + public string? ServiceName { get; set; } + + // The address of the endpoint + [JsonPropertyName("address")] + public string? Address { get; set; } + + // The port of the endpoint + [JsonPropertyName("port")] + public int? Port { get; set; } +} + +public class EndpointStatus : V1Status +{ + // Currently Endpoint has no status properties, but that may change in future. +} + +public class Endpoint : CustomResource +{ + [JsonConstructor] + public Endpoint(EndpointSpec spec) : base(spec) { } + + public static Endpoint Create(string name, string serviceNamespace, string serviceName) + { + var e = new Endpoint(new EndpointSpec + { + ServiceName = serviceName, + ServiceNamespace = serviceNamespace, + }); + + e.Kind = Dcp.EndpointKind; + e.ApiVersion = Dcp.GroupVersion.ToString(); + e.Metadata.Name = name; + e.Metadata.NamespaceProperty = string.Empty; + + return e; + } +} + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/Executable.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/Executable.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/Executable.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/Executable.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.Dcp.Model; + +using System.Text.Json.Serialization; +using k8s.Models; + +public class ExecutableSpec +{ + // Path to Executable binary + [JsonPropertyName("executablePath")] + public string? ExecutablePath { get; set; } + + // The working directory for the Executable + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } + + // Launch arguments to be passed to the Executable + [JsonPropertyName("args")] + public List? Args { get; set; } + + // Environment variables to be set for the Executable + [JsonPropertyName("env")] + public List? Env { get; set; } + + // Environment files to use to populate Executable environment during startup. + [JsonPropertyName("envFiles")] + public List? EnvFiles { get; set; } + + // The execution type for the Executable + [JsonPropertyName("executionType")] + public string? ExecutionType { get; set; } +} + +public static class ExecutionType +{ + // Executable will be run directly by the controller, as a child process + public static readonly string Process = "Process"; + + // Executable will be run via an IDE such as Visual Studio or Visual Studio Code. + public static readonly string IDE = "IDE"; +} + +public class ExecutableStatus : V1Status +{ + // The execution ID is the identifier for the actual-state counterpart of the Executable. + // For ExecutionType == Process it is the process ID. Process IDs will be eventually reused by OS, + // but a combination of process ID and startup timestamp is unique for each Executable instance. + // For ExecutionType == IDE it is the IDE session ID. + [JsonPropertyName("executionID")] + public string? ExecutionID { get; set; } + + [JsonPropertyName("pid")] + public int ProcessId { get; set; } + + // The current state of the process/IDE session started for this executable + [JsonPropertyName("state")] + public string? State { get; set; } = ExecutableStates.Unknown; + + // Start (attempt) timestamp. + [JsonPropertyName("startupTimestamp")] + public DateTimeOffset? StartupTimestamp { get; set; } + + // The time when the replica finished execution + [JsonPropertyName("finishTimestamp")] + public DateTimeOffset? FinishTimestamp { get; set; } + + // Exit code of the process associated with the Executable. + // The value is equal to UnknownExitCode if the Executable was not started, is still running, or the exit code is not available. + [JsonPropertyName("exitCode")] + public int? ExitCode { get; set; } + + // The path of a temporary file that contains captured standard output data from the Executable process. + [JsonPropertyName("stdOutFile")] + public string? StdOutFile { get; set; } + + // The path of a temporary file that contains captured standard error data from the Executable process. + [JsonPropertyName("stdErrFile")] + public string? StdErrFile { get; set; } + + // Effective values of environment variables, after all substitutions have been applied + [JsonPropertyName("effectiveEnv")] + public List? EffectiveEnv { get; set; } + +} + +public static class ExecutableStates +{ + // Executable was successfully started and was running last time we checked. + public static readonly string Running = "Running"; + + // Terminated means the Executable was killed by the controller (e.g. as a result of scale-down, or object deletion). + public static readonly string Terminated = "Terminated"; + + // Failed to start means the Executable could not be started (e.g. because of invalid path to program file). + public static readonly string FailedToStart = "FailedToStart"; + + // Finished means the Executable ran to completion. + public static readonly string Finished = "Finished"; + + // Unknown means we are not tracking the actual-state counterpart of the Executable (process or IDE run session). + // As a result, we do not know whether it already finished, and what is the exit code, if any. + // This can happen if a controller launches a process and then terminates. + // When a new controller instance comes online, it may see non-zero ExecutionID Status, + // but it does not track the corresponding process or IDE session. + public static readonly string Unknown = "Unknown"; +} + +public class Executable : CustomResource +{ + public static readonly string CSharpProjectPathAnnotation = "csharp-project-path"; + public static readonly string LaunchProfileNameAnnotation = "launch-profile-name"; + + [JsonConstructor] + public Executable(ExecutableSpec spec) : base(spec) { } + + public static Executable Create(string name, string executablePath) + { + var exe = new Executable(new ExecutableSpec + { + ExecutablePath = executablePath, + }); + exe.Kind = Dcp.ExecutableKind; + exe.ApiVersion = Dcp.GroupVersion.ToString(); + exe.Metadata.Name = name; + exe.Metadata.NamespaceProperty = string.Empty; + + return exe; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/ExecutableReplicaSet.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/ExecutableReplicaSet.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/ExecutableReplicaSet.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/ExecutableReplicaSet.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,99 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.Dcp.Model; + +using System.Text.Json.Serialization; +using k8s.Models; + +public class ExecutableTemplate : IAnnotationHolder +{ + // Labels to apply to child Executable objects + [JsonPropertyName("labels")] + public IDictionary? Labels { get; set; } + + // Annotations to apply to child Executable objects + [JsonPropertyName("annotations")] + public IDictionary? Annotations { get; set; } + + // Spec for the child Executable + [JsonPropertyName("spec")] + public ExecutableSpec Spec { get; set; } = new ExecutableSpec(); + + public void Annotate(string annotationName, string value) + { + if (Annotations is null) + { + Annotations = new Dictionary(); + } + + Annotations[annotationName] = value; + } + + public void AnnotateAsObjectList(string annotationName, TValue value) + { + if (Annotations is null) + { + Annotations = new Dictionary(); + } + + CustomResource.AnnotateAsObjectList(Annotations, annotationName, value); + } +} + +public class ExecutableReplicaSetSpec +{ + // Number of desired child Executable objects + [JsonPropertyName("replicas")] + public int Replicas { get; set; } = 1; + + // Template describing the configuration of child Executable objects created by the replica set + [JsonPropertyName("template")] + public ExecutableTemplate Template { get; set; } = new ExecutableTemplate(); +} + +public class ExecutableReplicaSetStatus : V1Status +{ + // Total number of observed child executables + [JsonPropertyName("observedReplicas")] + public int? ObservedReplicas { get; set; } + + // Total number of current running child Executables + [JsonPropertyName("runningReplicas")] + public int? RunningReplicas { get; set; } + + // Total number of current Executable replicas that failed to start + [JsonPropertyName("failedReplicas")] + public int? FailedReplicas { get; set; } + + // Total number of current child Executables that have finished running + [JsonPropertyName("finishedReplicas")] + public int? FinishedReplicas { get; set; } + + // Last time the replica set was scaled up or down by the controller + [JsonPropertyName("lastScaleTime")] + public DateTimeOffset? LastScaleTime { get; set; } +} + +public class ExecutableReplicaSet : CustomResource +{ + [JsonConstructor] + public ExecutableReplicaSet(ExecutableReplicaSetSpec spec) : base(spec) { } + + public static ExecutableReplicaSet Create(string name, int replicas, string executablePath) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(replicas, nameof(replicas)); + + var ers = new ExecutableReplicaSet(new ExecutableReplicaSetSpec + { + Replicas = replicas + }); + ers.Kind = Dcp.ExecutableReplicaSetKind; + ers.ApiVersion = Dcp.GroupVersion.ToString(); + ers.Metadata.Name = name; + ers.Metadata.NamespaceProperty = string.Empty; + ers.Spec.Template.Spec.ExecutablePath = executablePath; + + return ers; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/GroupVersion.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/GroupVersion.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/GroupVersion.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/GroupVersion.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.Dcp.Model; + +public struct GroupVersion +{ + public string Group { get; set; } + public string Version { get; set; } + + public override string ToString() => $"{Group}/{Version}"; +} + +public static class Dcp +{ + public static GroupVersion GroupVersion { get; } = new GroupVersion + { + Group = "usvc-dev.developer.microsoft.com", + Version = "v1" + }; + + public static readonly Schema Schema = new(); + + public static string ExecutableKind { get; } = "Executable"; + public static string ContainerKind { get; } = "Container"; + public static string ServiceKind { get; } = "Service"; + public static string EndpointKind { get; } = "Endpoint"; + public static string ExecutableReplicaSetKind { get; } = "ExecutableReplicaSet"; + + static Dcp() + { + Schema.Add(ExecutableKind, "executables"); + Schema.Add(ContainerKind, "containers"); + Schema.Add(ServiceKind, "services"); + Schema.Add(EndpointKind, "endpoints"); + Schema.Add(ExecutableReplicaSetKind, "executablereplicasets"); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/ModelCommon.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/ModelCommon.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/ModelCommon.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/ModelCommon.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,179 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.Dcp.Model; + +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; +using k8s; +using k8s.Models; + +public interface IAnnotationHolder +{ + void Annotate(string annotationName, string value); + void AnnotateAsObjectList(string annotationName, TValue value); +} + +public abstract class CustomResource : KubernetesObject, IMetadata, IAnnotationHolder +{ + public static readonly string ServiceProducerAnnotation = "service-producer"; + public static readonly string ServiceConsumerAnnotation = "service-consumer"; + public static readonly string UriSchemeAnnotation = "uri-scheme"; + + [JsonPropertyName("metadata")] + public V1ObjectMeta Metadata { get; set; } = new V1ObjectMeta(); + + public void Annotate(string annotationName, string value) + { + if (Metadata.Annotations is null) + { + Metadata.Annotations = new Dictionary(); + } + + Metadata.Annotations[annotationName] = value; + } + + public void AnnotateAsObjectList(string annotationName, TValue value) + { + if (Metadata.Annotations is null) + { + Metadata.Annotations = new Dictionary(); + } + + AnnotateAsObjectList(Metadata.Annotations, annotationName, value); + } + + internal static void AnnotateAsObjectList(IDictionary annotations, string annotationName, TValue value) + { + List values; + if (annotations.TryGetValue(annotationName, out var annotationVal)) + { + values = JsonSerializer.Deserialize>(annotationVal) ?? new(); + if (!values.Contains(value)) + { + values.Add(value); + } + } + else + { + values = new(); + values.Add(value); + } + + var newAnnotationVal = JsonSerializer.Serialize(values); + annotations[annotationName] = newAnnotationVal; + } +} + +public abstract class CustomResource : CustomResource +{ + [JsonPropertyName("spec")] + public TSpec Spec { get; set; } + + [JsonPropertyName("status")] + public TStatus? Status { get; set; } + + public CustomResource(TSpec spec) + { + Spec = spec; + } +} + +public class CustomResourceList : KubernetesObject +where T : CustomResource +{ + [JsonPropertyName("metadata")] + public V1ListMeta Metadata { get; set; } = new V1ListMeta(); + + [JsonPropertyName("items")] + public required List Items { get; set; } +} + +public class EnvVar +{ + // Name of the environment variable + [JsonPropertyName("name")] + public string? Name { get; set; } + + // Value of the environment variable + [JsonPropertyName("value")] + public string? Value { get; set; } +} + +public static class Conventions +{ + // Indicates that process ID of some process is not known + public const int UnknownPID = -1; + + // Indicates that the exit code of some process is not known + public const int UnknownExitCode = -1; +} + +public class ServiceProducerAnnotation +{ + // Name of the service produced + [JsonPropertyName("serviceName")] + public string ServiceName { get; set; } + + // Desired address for the service. + // If not set, the service must specify the placeholder for address injection via addressFor template function. + [JsonPropertyName("address")] + public string? Address { get; set; } + + // The port that the program will use to implement the service endpoint. + // If not set, the service must specify the placeholder for address injection via portFor template function. + [JsonPropertyName("port")] + public int? Port { get; set; } + + [JsonConstructor] + public ServiceProducerAnnotation(string serviceName) + { + ServiceName = serviceName; + } + + public override bool Equals(object? obj) + { + if (obj is null || obj is not ServiceProducerAnnotation) { return false; } + + var other = (ServiceProducerAnnotation)obj; + + if (!string.Equals(ServiceName, other.ServiceName, StringComparison.Ordinal)) { return false; } + + // Note: string.Equals(null, null) is true, which is what we want here. + if (!string.Equals(Address, other.Address, StringComparison.Ordinal)) { return false; } + + if (Port != other.Port) { return false; } + + return true; + } + + public override int GetHashCode() + { + return HashCode.Combine(ServiceName, Address, Port); + } +} + +public sealed record NamespacedName(string Name, string? Namespace); + +public static class Rules +{ + public static bool IsValidObjectName(string candidate) + { + if (string.IsNullOrWhiteSpace(candidate)) + { + return false; + } + + if (candidate.Length > 253) + { + return false; + } + + // Can only contain alphanumeric characters, hyphen, period, underscore, tilde. + // (essentially the same as URL path characters). + // Needs to start with a letter, underscore, or tilde. + bool isValid = Regex.IsMatch(candidate, @"^[[a-zA-Z_~][a-zA-Z0-9\-._~]*$"); + return isValid; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/Schema.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/Schema.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/Schema.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/Schema.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.Dcp.Model; + +public class Schema +{ + private readonly Dictionary _byType = new(); + + public void Add(string kind, string resource) where T : CustomResource + { + _byType.Add(typeof(T), (kind, resource)); + } + + public bool TryGet(out (string Kind, string Resource) kindWithResource) where T : CustomResource + { + return _byType.TryGetValue(typeof(T), out kindWithResource); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/Service.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/Service.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Model/Service.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Model/Service.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization; +using k8s.Models; + +namespace Aspire.Hosting.Dcp.Model; + +public class ServiceSpec +{ + // The desired address for the service to run on + [JsonPropertyName("address")] + public string? Address { get; set; } + + // The desired port for the service to run on + [JsonPropertyName("port")] + public int? Port { get; set; } + + // The network protocol to be used for the service. Defaults to TCP. + [JsonPropertyName("protocol")] + public string Protocol { get; set; } = PortProtocol.TCP; + + // The mode for address allocation + [JsonPropertyName("addressAllocationMode")] + public string AddressAllocationMode = AddressAllocationModes.Localhost; +} + +public class ServiceStatus : V1Status +{ + // The actual address the service is running on + [JsonPropertyName("effectiveAddress")] + public string? EffectiveAddress { get; set; } + + // The actual port the service is running on + [JsonPropertyName("effectivePort")] + public int? EffectivePort { get; set; } + + // The current state of the service + [JsonPropertyName("state")] + public string? State { get; set; } +} + +public static class ServiceState +{ + // The service is not ready to accept connection. EffectiveAddress and EffectivePort do not contain final data. + public static readonly string NotReady = "NotReady"; + + // The service is ready to accept connections. + public static readonly string Ready = "Ready"; +} + +public static class AddressAllocationModes +{ + // Bind only to 127.0.0.1 + public static readonly string IPv4ZeroOne = "IPv4ZeroOne"; + + // Bind to any 127.*.*.* loopback address range + public static readonly string IPv4Loopback = "IPv4Loopback"; + + // Bind to IPv6 ::1 + public static readonly string IPv6 = "IPv6ZeroOne"; + + // Bind to "localhost", which is all loopback devices on the machine. + public static readonly string Localhost = "Localhost"; +} + +public class Service : CustomResource +{ + [JsonConstructor] + public Service(ServiceSpec spec) : base(spec) { } + + public static Service Create(string name) + { + var s = new Service(new ServiceSpec()); + + s.Kind = Dcp.ServiceKind; + s.ApiVersion = Dcp.GroupVersion.ToString(); + s.Metadata.Name = name; + s.Metadata.NamespaceProperty = string.Empty; + + return s; + } + + public int? AllocatedPort => Spec.Port ?? Status?.EffectivePort; + public string? AllocatedAddress => Spec.Address ?? Status?.EffectiveAddress; + public bool HasCompleteAddress => AllocatedPort > 0 && !string.IsNullOrEmpty(AllocatedAddress); + + public void ApplyAddressInfoFrom(Service other) + { + Spec.Port = other.Spec.Port; + Spec.Address = other.Spec.Address; + if (Status is null) + { + Status = new(); + } + if (other.Status?.EffectiveAddress is not null) + { + Status.EffectiveAddress = other.Status.EffectiveAddress; + } + if (other.Status?.EffectivePort is not null) + { + Status.EffectivePort = other.Status.EffectivePort; + } + } +} + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Process/ProcessResult.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Process/ProcessResult.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Process/ProcessResult.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Process/ProcessResult.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.Dcp.Process; + +internal sealed class ProcessResult +{ + public ProcessResult(int exitCode) + { + ExitCode = exitCode; + } + + public int ExitCode { get; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Process/ProcessSpec.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Process/ProcessSpec.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Process/ProcessSpec.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Process/ProcessSpec.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.Dcp.Process; + +internal sealed class ProcessSpec +{ + public string ExecutablePath { get; set; } + public string? WorkingDirectory { get; set; } + public IDictionary EnvironmentVariables { get; set; } = new Dictionary(); + public string? Arguments { get; set; } + public Action? OnOutputData { get; set; } + public Action? OnErrorData { get; set; } + public Action? OnStart { get; set; } + public Action? OnStop { get; set; } + public bool KillEntireProcessTree { get; set; } = true; + public bool ThrowOnNonZeroReturnCode { get; set; } = true; + + public ProcessSpec(string executablePath) + { + ExecutablePath = executablePath; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Process/ProcessUtil.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Process/ProcessUtil.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Dcp/Process/ProcessUtil.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Dcp/Process/ProcessUtil.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,157 @@ +// 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; +using System.Runtime.InteropServices; + +namespace Aspire.Hosting.Dcp.Process; + +internal static partial class ProcessUtil +{ + #region Native Methods + + [LibraryImport("libc", SetLastError = true, EntryPoint = "kill")] + private static partial int sys_kill(int pid, int sig); + + #endregion + + private static readonly TimeSpan s_processExitTimeout = TimeSpan.FromSeconds(5); + + public static (Task, IAsyncDisposable) Run(ProcessSpec processSpec) + { + var process = new System.Diagnostics.Process() + { + StartInfo = + { + FileName = processSpec.ExecutablePath, + WorkingDirectory = processSpec.WorkingDirectory ?? string.Empty, + Arguments = processSpec.Arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden, + }, + EnableRaisingEvents = true + }; + + processSpec.EnvironmentVariables.ToList().ForEach(x => process.StartInfo.Environment[x.Key] = x.Value); + + var processEventLock = new object(); + + // Note: even though the child process has exited, its children may be alive and still producing output. + // See https://github.com/dotnet/runtime/issues/29232#issuecomment-1451584094 for how this might affect waiting for process exit. + // We are going to discard that (grandchild) output by checking process.HasExited. + + if (processSpec.OnOutputData != null) + { + process.OutputDataReceived += (_, e) => + { + lock (processEventLock) + { + if (e.Data == null || process.HasExited) + { + return; + } + } + + processSpec.OnOutputData.Invoke(e.Data); // not holding the event lock + }; + } + + if (processSpec.OnErrorData != null) + { + process.ErrorDataReceived += (_, e) => + { + lock (processEventLock) + { + if (e.Data == null || process.HasExited) + { + return; + } + } + + processSpec.OnErrorData.Invoke(e.Data); // not holding the event lock + }; + } + + var processLifetimeTcs = new TaskCompletionSource(); + + process.Exited += (_, e) => + { + lock (processEventLock) + { + if (processSpec.ThrowOnNonZeroReturnCode && process.ExitCode != 0) + { + processLifetimeTcs.TrySetException(new InvalidOperationException( + $"Command {processSpec.ExecutablePath} {processSpec.Arguments} returned non-zero exit code {process.ExitCode}")); + } + else + { + processLifetimeTcs.TrySetResult(new ProcessResult(process.ExitCode)); + } + } + }; + + try + { + AspireEventSource.Instance.ProcessLaunchStart(processSpec.ExecutablePath, processSpec.Arguments ?? ""); + + // Take the event lock to ensure that OnStart() is called before the lifetime task ends. + lock (processEventLock) + { + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + processSpec.OnStart?.Invoke(process.Id); + } + } + finally + { + AspireEventSource.Instance.ProcessLaunchStop(processSpec.ExecutablePath, processSpec.Arguments ?? ""); + } + + + return (processLifetimeTcs.Task, new ProcessDisposable(process, processLifetimeTcs.Task, processSpec.KillEntireProcessTree)); + } + + private sealed class ProcessDisposable : IAsyncDisposable + { + private readonly System.Diagnostics.Process _process; + private readonly Task _processLifetimeTask; + private readonly bool _entireProcessTree; + + public ProcessDisposable(System.Diagnostics.Process process, Task processLifetimeTask, bool entireProcessTree) + { + _process = process; + _processLifetimeTask = processLifetimeTask; + _entireProcessTree = entireProcessTree; + } + + public async ValueTask DisposeAsync() + { + if (_process.HasExited) + { + return; // nothing to do + } + + if (OperatingSystem.IsWindows()) + { + if (!_process.CloseMainWindow()) + { + _process.Kill(_entireProcessTree); + } + } + else + { + sys_kill(_process.Id, sig: 2); // SIGINT + } + + await _processLifetimeTask.WaitAsync(s_processExitTimeout).ConfigureAwait(false); + if (!_process.HasExited) + { + // Always try to kill the entire process tree here if all of the above has failed. + _process.Kill(entireProcessTree: true); + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/DistributedApplicationBuilder.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/DistributedApplicationBuilder.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/DistributedApplicationBuilder.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/DistributedApplicationBuilder.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,99 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Dcp; +using Aspire.Hosting.Lifecycle; +using Aspire.Hosting.Publishing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Aspire.Hosting; + +public class DistributedApplicationBuilder : IDistributedApplicationBuilder +{ + private readonly HostApplicationBuilder _innerBuilder; + private readonly string[] _args; + + public IHostEnvironment Environment => _innerBuilder.Environment; + + public ConfigurationManager Configuration => _innerBuilder.Configuration; + + public IServiceCollection Services => _innerBuilder.Services; + + public IResourceCollection Resources { get; } = new ResourceCollection(); + + public DistributedApplicationBuilder(DistributedApplicationOptions options) + { + _args = options.Args ?? []; + _innerBuilder = new HostApplicationBuilder(); + + // Core things + _innerBuilder.Services.AddSingleton(sp => new DistributedApplicationModel(Resources)); + _innerBuilder.Services.AddHostedService(); + _innerBuilder.Services.AddSingleton(options); + + // DCP stuff + _innerBuilder.Services.AddLifecycleHook(); + _innerBuilder.Services.AddSingleton(); + _innerBuilder.Services.AddHostedService(); + + // We need a unique path per application instance + var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + _innerBuilder.Services.AddSingleton(new Locations(path)); + _innerBuilder.Services.AddSingleton(); + + // Publishing support + ConfigurePublishingOptions(options); + _innerBuilder.Services.AddLifecycleHook(); + _innerBuilder.Services.AddLifecycleHook(); + _innerBuilder.Services.AddKeyedSingleton("manifest"); + _innerBuilder.Services.AddKeyedSingleton("dcp"); + } + + private void ConfigurePublishingOptions(DistributedApplicationOptions options) + { + var switchMappings = new Dictionary() + { + { "--publisher", "Publishing:Publisher" }, + { "--output-path", "Publishing:OutputPath" }, + { "--dcp-cli-path", "DcpPublisher:CliPath" }, + }; + _innerBuilder.Configuration.AddCommandLine(options.Args ?? [], switchMappings); + _innerBuilder.Services.Configure(_innerBuilder.Configuration.GetSection(PublishingOptions.Publishing)); + _innerBuilder.Services.Configure( + o => o.ApplyApplicationConfiguration( + options, + dcpPublisherConfiguration: _innerBuilder.Configuration.GetSection(DcpOptions.DcpPublisher), + publishingConfiguration: _innerBuilder.Configuration.GetSection(PublishingOptions.Publishing) + ) + ); + } + + public DistributedApplication Build() + { + AspireEventSource.Instance.DistributedApplicationBuildStart(); + try + { + var application = new DistributedApplication(_innerBuilder.Build(), _args); + return application; + } + finally + { + AspireEventSource.Instance.DistributedApplicationBuildStop(); + } + } + + public IResourceBuilder AddResource(T resource) where T : IResource + { + if (Resources.FirstOrDefault(r => r.Name == resource.Name) is { } existingResource) + { + throw new DistributedApplicationException($"Cannot add resource of type '{resource.GetType()}' with name '{resource.Name}' because resource of type '{existingResource.GetType()}' with that name already exists."); + } + + Resources.Add(resource); + var builder = new DistributedApplicationResourceBuilder(this, resource); + return builder; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/DistributedApplication.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/DistributedApplication.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/DistributedApplication.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/DistributedApplication.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,103 @@ +// 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; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Lifecycle; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Aspire.Hosting; + +public enum DockerHealthCheckFailures : int +{ + Unresponsive = 125, // Invocation of Docker CLI test command did not finish within expected time period. + Unhealthy = 126, // The Docker CLI test command returned an error exit code. + PrerequisiteMissing = 127 // We could not invoke Docker CLI, Docker may be missing from the machine. +} + +[DebuggerDisplay("{_host}")] +public class DistributedApplication : IHost, IAsyncDisposable +{ + private readonly IHost _host; + private readonly string[] _args; + + public DistributedApplication(IHost host, string[] args) + { + _host = host; + _args = args; + } + + public static IDistributedApplicationBuilder CreateBuilder() => CreateBuilder([]); + + public static IDistributedApplicationBuilder CreateBuilder(string[] args) + { + var builder = new DistributedApplicationBuilder(new DistributedApplicationOptions() { Args = args }); + return builder; + } + + public static IDistributedApplicationBuilder CreateBuilder(DistributedApplicationOptions options) + { + var builder = new DistributedApplicationBuilder(options); + return builder; + } + + public IServiceProvider Services => _host.Services; + + public void Dispose() + { + _host.Dispose(); + } + + public ValueTask DisposeAsync() + { + return ((IAsyncDisposable)_host).DisposeAsync(); + } + + public async Task StartAsync(CancellationToken cancellationToken = default) + { + await ExecuteBeforeStartHooksAsync(cancellationToken).ConfigureAwait(false); + await _host.StartAsync(cancellationToken).ConfigureAwait(false); + } + + public async Task StopAsync(CancellationToken cancellationToken = default) + { + await _host.StopAsync(cancellationToken).ConfigureAwait(false); + } + + public async Task RunAsync(CancellationToken cancellationToken = default) + { + await ExecuteBeforeStartHooksAsync(cancellationToken).ConfigureAwait(false); + await _host.RunAsync(cancellationToken).ConfigureAwait(false); + } + + public void Run() + { + RunAsync().Wait(); + } + + private async Task ExecuteBeforeStartHooksAsync(CancellationToken cancellationToken) + { + AspireEventSource.Instance.AppBeforeStartHooksStart(); + + try + { + var lifecycleHooks = _host.Services.GetServices(); + var appModel = _host.Services.GetRequiredService(); + + foreach (var lifecycleHook in lifecycleHooks) + { + await lifecycleHook.BeforeStartAsync(appModel, cancellationToken).ConfigureAwait(false); + } + } + finally + { + AspireEventSource.Instance.AppBeforeStartHooksStop(); + } + } + + Task IHost.StartAsync(CancellationToken cancellationToken) => StartAsync(cancellationToken); + + Task IHost.StopAsync(CancellationToken cancellationToken) => StopAsync(cancellationToken); +} + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/DistributedApplicationException.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/DistributedApplicationException.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/DistributedApplicationException.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/DistributedApplicationException.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting; + +public class DistributedApplicationException : Exception +{ + public DistributedApplicationException() { } + public DistributedApplicationException(string message) : base(message) { } + public DistributedApplicationException(string message, Exception inner) : base(message, inner) { } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/DistributedApplicationOptions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/DistributedApplicationOptions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/DistributedApplicationOptions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/DistributedApplicationOptions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting; + +/// +/// Options for configuring the behavior of . +/// +public sealed class DistributedApplicationOptions +{ + /// + /// The command line arguments. + /// + public string[]? Args { get; set; } + + /// + /// The AssemblyName of the AppHost project for loading configuration attributes; if not set defaults to Assembly.GetEntryAssembly(). + /// + public string? AssemblyName { get; set; } + + /// + /// Determines whether the dashboard is disabled. + /// + public bool DisableDashboard { get; set; } + + internal bool DashboardEnabled => !DisableDashboard; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/DistributedApplicationResourceBuilder.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/DistributedApplicationResourceBuilder.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/DistributedApplicationResourceBuilder.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/DistributedApplicationResourceBuilder.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting; + +internal sealed class DistributedApplicationResourceBuilder(IDistributedApplicationBuilder applicationBuilder, T resource) : IResourceBuilder where T : IResource +{ + public T Resource { get; } = resource; + public IDistributedApplicationBuilder ApplicationBuilder { get; } = applicationBuilder; + + public IResourceBuilder WithAnnotation(TAnnotation annotation) where TAnnotation : IResourceAnnotation + { + Resource.Annotations.Add(annotation); + return this; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/DistributedApplicationRunner.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/DistributedApplicationRunner.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/DistributedApplicationRunner.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/DistributedApplicationRunner.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Publishing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; + +namespace Aspire.Hosting; + +internal sealed class DistributedApplicationRunner(DistributedApplicationModel model, IOptions options, IServiceProvider serviceProvider) : BackgroundService +{ + private readonly DistributedApplicationModel _model = model; + private readonly IOptions _options = options; + private readonly IServiceProvider _serviceProvider = serviceProvider; + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var publisherName = _options.Value.Publisher?.ToLowerInvariant() ?? "dcp"; + + var publisher = _serviceProvider.GetKeyedService(publisherName) + ?? throw new DistributedApplicationException($"Could not find registered publisher '{publisherName}'"); + + await publisher.PublishAsync(_model, stoppingToken).ConfigureAwait(false); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Extensions/ContainerResourceBuilderExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Extensions/ContainerResourceBuilderExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Extensions/ContainerResourceBuilderExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Extensions/ContainerResourceBuilderExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Sockets; +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting; + +public static class ContainerResourceBuilderExtensions +{ + /// + /// Adds a container resource to the application. Uses the "latest" tag. + /// + /// The . + /// The name of the resource. + /// The container image name. The tag is assumed to be "latest". + /// The for chaining. + public static IResourceBuilder AddContainer(this IDistributedApplicationBuilder builder, string name, string image) + { + return builder.AddContainer(name, image, "latest"); + } + + /// + /// Adds a container resource to the application. + /// + /// The . + /// The name of the resource. + /// The container image name. + /// The container image tag. + /// The for chaining. + public static IResourceBuilder AddContainer(this IDistributedApplicationBuilder builder, string name, string image, string tag) + { + var container = new ContainerResource(name); + return builder.AddResource(container) + .WithAnnotation(new ContainerImageAnnotation { Image = image, Tag = tag }); + } + + /// + /// Adds a binding to expose an endpoint on a resource. + /// + /// The resource type. + /// The resoure builder. + /// The container port. + /// The host machine port. + /// The scheme e.g http/https/amqp + /// The name of the binding. + /// The . + public static IResourceBuilder WithServiceBinding(this IResourceBuilder builder, int containerPort, int? hostPort = null, string? scheme = null, string? name = null) where T : IResource + { + if (builder.Resource.Annotations.OfType().Any(sb => sb.Name == name)) + { + throw new DistributedApplicationException($"Service binding with name '{name}' already exists"); + } + + var annotation = new ServiceBindingAnnotation( + protocol: ProtocolType.Tcp, + uriScheme: scheme, + name: name, + port: hostPort, + containerPort: containerPort); + + return builder.WithAnnotation(annotation); + } + + /// + /// Adds a volume mount to a container resource. + /// + /// The resource type. + /// The resoure builder. + /// The source path of the volume. This is the physical location on the host. + /// The target path in the container. + /// The type of volume mount. + /// A flag that indicates if this is a read-only mount. + /// The . + public static IResourceBuilder WithVolumeMount(this IResourceBuilder builder, string source, string target, VolumeMountType type = default, bool isReadOnly = false) where T : ContainerResource + { + var annotation = new VolumeMountAnnotation(source, target, type, isReadOnly); + return builder.WithAnnotation(annotation); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Extensions/ContainerResourceExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Extensions/ContainerResourceExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Extensions/ContainerResourceExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Extensions/ContainerResourceExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting; + +public static class ContainerResourceExtensions +{ + public static IEnumerable GetContainerResources(this DistributedApplicationModel model) + { + foreach (var resource in model.Resources) + { + if (resource.Annotations.OfType().Any()) + { + yield return resource; + } + } + } + + public static bool IsContainer(this IResource resource) + { + return resource.Annotations.OfType().Any(); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Extensions/ExecutableResourceBuilderExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Extensions/ExecutableResourceBuilderExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Extensions/ExecutableResourceBuilderExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Extensions/ExecutableResourceBuilderExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting; + +public static class ExecutableResourceBuilderExtensions +{ + /// + /// Adds an executable resource to the application model. + /// + /// The . + /// The name of the resource. + /// The executable path. This can be a fully qualified path or a executable to run from the shell/command line. + /// The working directory of the executable. + /// The arguments to the executable. + /// The . + public static IResourceBuilder AddExecutable(this IDistributedApplicationBuilder builder, string name, string command, string workingDirectory, params string[]? args) + { + var executable = new ExecutableResource(name, command, workingDirectory, args); + return builder.AddResource(executable); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Extensions/ExecutableResourceExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Extensions/ExecutableResourceExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Extensions/ExecutableResourceExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Extensions/ExecutableResourceExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting; + +public static class ExecutableResourceExtensions +{ + public static IEnumerable GetExecutableResources(this DistributedApplicationModel model) + { + return model.Resources.OfType(); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Extensions/ProjectResourceBuilderExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Extensions/ProjectResourceBuilderExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Extensions/ProjectResourceBuilderExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Extensions/ProjectResourceBuilderExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Dashboard; +using Aspire.Hosting.Properties; + +namespace Aspire.Hosting; + +public static class ProjectResourceBuilderExtensions +{ + /// + /// Adds a .NET project to the application model. By default, this will exist in a Projects namespace. e.g. Projects.MyProject. + /// If the project is not in a Projects namespace, make sure a project reference is added from the AppHost project to the target project. + /// + /// A type that represents the project reference. + /// The . + /// The name of the resource. This name will be used for service discovery when referenced in a dependency. + /// A reference to the . + public static IResourceBuilder AddProject(this IDistributedApplicationBuilder builder, string name) where TProject : IServiceMetadata, new() + { + var project = new ProjectResource(name); + var projectBuilder = builder.AddResource(project); + // We only want to turn these on for .NET projects, ConfigureOtlpEnvironment works for any resource type that + // implements IDistributedApplicationResourceWithEnvironment. + projectBuilder.WithEnvironment("OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES", "true"); + projectBuilder.WithEnvironment("OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES", "true"); + projectBuilder.WithOtlpExporter(); + projectBuilder.ConfigureConsoleLogs(); + var serviceMetadata = new TProject(); + projectBuilder.WithAnnotation(serviceMetadata); + return projectBuilder; + } + + /// + /// Configures how many replicas of the project should be created for the project. + /// + /// The project resource builder. + /// The number of replicas. + /// A reference to the . + public static IResourceBuilder WithReplicas(this IResourceBuilder builder, int replicas) + { + builder.WithAnnotation(new ReplicaAnnotation(replicas)); + return builder; + } + + /// + /// Configures how many replicas of the project should be created for the project. + /// + /// The executable resource builder. + /// The number of replicas. + /// A reference to the . + public static IResourceBuilder WithReplicas(this IResourceBuilder builder, int replicas) + { + builder.WithAnnotation(new ReplicaAnnotation(replicas)); + return builder; + } + + /// + /// Configures which launch profile should be used when running the project. + /// + /// The project resource builder. + /// The name of the launch profile to use for execution. + /// A reference to the . + public static IResourceBuilder WithLaunchProfile(this IResourceBuilder builder, string launchProfileName) + { + ArgumentException.ThrowIfNullOrEmpty(launchProfileName); + + var launchSettings = builder.Resource.GetLaunchSettings(); + + if (launchSettings == null) + { + throw new DistributedApplicationException(Resources.LaunchProfileIsSpecifiedButLaunchSettingsFileIsNotPresentExceptionMessage); + } + + if (!launchSettings.Profiles.TryGetValue(launchProfileName, out var launchProfile)) + { + var message = string.Format(CultureInfo.InvariantCulture, Resources.LaunchSettingsFileDoesNotContainProfileExceptionMessage, launchProfileName); + throw new DistributedApplicationException(message); + } + + var launchProfileAnnotation = new LaunchProfileAnnotation(launchProfileName, launchProfile); + return builder.WithAnnotation(launchProfileAnnotation); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Extensions/ResourceBuilderExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Extensions/ResourceBuilderExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Extensions/ResourceBuilderExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Extensions/ResourceBuilderExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,275 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Sockets; +using Aspire.Hosting.ApplicationModel; +using Microsoft.Extensions.Configuration; + +namespace Aspire.Hosting; + +public static class ResourceBuilderExtensions +{ + private const string ConnectionStringEnvironmentName = "ConnectionStrings__"; + + /// + /// Adds an environment variable to the resource. + /// + /// The resource type. + /// The resource builder. + /// The name of the environment variable. + /// The value of the environment variable. + /// A resource configured with the specified environment variable. + public static IResourceBuilder WithEnvironment(this IResourceBuilder builder, string name, string? value) where T : IResource + { + return builder.WithAnnotation(new EnvironmentCallbackAnnotation(name, () => value ?? string.Empty)); + } + + /// + /// Adds an environment variable to the resource. + /// + /// The resource type. + /// The resource builder. + /// The name of the environment variable. + /// A callback that allows for deferred execution of a specific enviroment variable. This runs after resources have been allocated by the orchestrator and allows access to other resources to resolve computed data, e.g. connetion strings, ports. + /// A resource configured with the specified environment variable. + public static IResourceBuilder WithEnvironment(this IResourceBuilder builder, string name, Func callback) where T : IResourceWithEnvironment + { + return builder.WithAnnotation(new EnvironmentCallbackAnnotation(name, callback)); + } + + /// + /// Allows for the population of environment variables on a resource. + /// + /// The resource type. + /// The resource builder. + /// A callback that allows for deferred execution for computing many enviroment variables. This runs after resources have been allocated by the orchestrator and allows access to other resources to resolve computed data, e.g. connetion strings, ports. + /// A resource configured with the environment variable callback. + public static IResourceBuilder WithEnvironment(this IResourceBuilder builder, Action callback) where T : IResourceWithEnvironment + { + return builder.WithAnnotation(new EnvironmentCallbackAnnotation(callback)); + } + + /// + /// Adds an environment variable to the resource with the binding for . + /// + /// The resource type. + /// The resource builder. + /// The name of the environment variable. + /// The endpoint from which to extract the service binding. + /// A resource configured with the environment variable callback. + public static IResourceBuilder WithEnvironment(this IResourceBuilder builder, string name, EndpointReference endpointReference) where T : IResourceWithEnvironment + { + return builder.WithAnnotation(new EnvironmentCallbackAnnotation(name, () => endpointReference.UriString)); + } + + private static bool ContainsAmbiguousEndpoints(IEnumerable endpoints) + { + // An ambiguous endpoint is where any scheme ( + return endpoints.GroupBy(e => e.UriScheme).Any(g => g.Count() > 1); + } + + private static Action CreateServiceReferenceEnvironmentPopulationCallback(ServiceReferenceAnnotation serviceReferencesAnnotation) + { + return (context) => + { + var name = serviceReferencesAnnotation.Resource.Name; + + var allocatedEndPoints = serviceReferencesAnnotation.Resource.Annotations + .OfType() + .Where(a => serviceReferencesAnnotation.UseAllBindings || serviceReferencesAnnotation.BindingNames.Contains(a.Name)); + + var containsAmiguousEndpoints = ContainsAmbiguousEndpoints(allocatedEndPoints); + + var i = 0; + foreach (var allocatedEndPoint in allocatedEndPoints) + { + var bindingNameQualifiedUriStringKey = $"services__{name}__{i++}"; + context.EnvironmentVariables[bindingNameQualifiedUriStringKey] = allocatedEndPoint.BindingNameQualifiedUriString; + + if (!containsAmiguousEndpoints) + { + var uriStringKey = $"services__{name}__{i++}"; + context.EnvironmentVariables[uriStringKey] = allocatedEndPoint.UriString; + } + } + }; + } + + /// + /// Injects a connection string as an environment variable from the source resource into the destination resource, using the source resource's name as the connection string name (if not overridden). + /// The format of the environment variable will be "ConnectionStrings__{sourceResourceName}={connectionString}." + /// + /// Each resource defines the format of the connection string value. The + /// underlying connection string value can be retrieved using . + /// + /// + /// Connection strings are also resolved by the configuration system (appSettings.json in the AppHost project, or environment variables). If a connection string is not found on the resource, the configuration system will be queried for a connection string + /// using the resource's name. + /// + /// + /// The destintion resource. + /// The resource where connection string will be injected. + /// The resource from which to extract the connection string. + /// An override of the source resource's name for the connection string. The resulting connection string will be "ConnectionStrings__connectionName" if this is not null. + /// to allow a missing connection string; to throw an exception if the connection string is not found. + /// Throws an exception if the connection string resolves to null. It can be null if the resource has no connection string, and if the configurtion has no connection string for the source resource. + /// A reference to the . + public static IResourceBuilder WithReference(this IResourceBuilder builder, IResourceBuilder source, string? connectionName = null, bool optional = false) + where TDestination : IResourceWithEnvironment + { + var resource = source.Resource; + connectionName ??= resource.Name; + + return builder.WithEnvironment(context => + { + var connectionStringName = $"{ConnectionStringEnvironmentName}{connectionName}"; + + if (context.PublisherName == "manifest") + { + context.EnvironmentVariables[connectionStringName] = $"{{{resource.Name}.connectionString}}"; + return; + } + + var connectionString = resource.GetConnectionString() ?? + builder.ApplicationBuilder.Configuration.GetConnectionString(resource.Name); + + if (string.IsNullOrEmpty(connectionString)) + { + if (optional) + { + // This is an optional connection string, so we can just return. + return; + } + + throw new DistributedApplicationException($"A connection string for '{resource.Name}' could not be retrieved."); + } + + context.EnvironmentVariables[connectionStringName] = connectionString; + }); + } + + /// + /// Injects service discovery information as environment variables from the project resource into the destination resource, using the source resource's name as the service name. + /// Each service binding defined on the project resource will be injected using the format "services__{sourceResourceName}__{bindingIndex}={bindingNameQualifiedUriString}." + /// + /// The destination resource. + /// The source project resource. + /// The resource where the service discovery information will be injected. + /// The resource from which to extract service bindings. + /// A reference to the . + public static IResourceBuilder WithReference(this IResourceBuilder builder, IResourceBuilder source) + where TDestination : IResourceWithEnvironment where TSource : ProjectResource + { + ApplyBinding(builder, source.Resource); + return builder; + } + + /// + /// Injects service discovery information from the specified endpoint into the project resource using the source resource's name as the service name. + /// Each service binding will be injected using the format "services__{sourceResourceName}__{bindingIndex}={bindingNameQualifiedUriString}." + /// + /// The destination resource. + /// The resource where the service discovery information will be injected. + /// The endpoint from which to extract the service binding. + /// A reference to the . + public static IResourceBuilder WithReference(this IResourceBuilder builder, EndpointReference endpointReference) + where TDestination : IResourceWithEnvironment + { + ApplyBinding(builder, endpointReference.Owner, endpointReference.BindingName); + return builder; + } + + private static void ApplyBinding(IResourceBuilder builder, IResourceWithBindings resourceWithBindings, string? bindingName = null) + where T : IResourceWithEnvironment + { + // When adding a service reference we get to see whether there is a ServiceReferencesAnnotation + // on the resource, if there is then it means we have already been here before and we can just + // skip this and note the service binding that we want to apply to the environment in the future + // in a single pass. There is one ServiceReferenceAnnotation per service binding source. + var serviceReferenceAnnotation = builder.Resource.Annotations + .OfType() + .Where(sra => sra.Resource == resourceWithBindings) + .SingleOrDefault(); + + if (serviceReferenceAnnotation == null) + { + serviceReferenceAnnotation = new ServiceReferenceAnnotation(resourceWithBindings); + builder.WithAnnotation(serviceReferenceAnnotation); + + var callback = CreateServiceReferenceEnvironmentPopulationCallback(serviceReferenceAnnotation); + builder.WithEnvironment(callback); + } + + // If no specific binding name is specified, go and add all the bindings. + if (bindingName == null) + { + serviceReferenceAnnotation.UseAllBindings = true; + } + else + { + serviceReferenceAnnotation.BindingNames.Add(bindingName); + } + } + + /// + /// Exposes an endpoint on a resource. This binding reference can be retrieved using . + /// The binding name will be the scheme name if not specified. + /// + /// The resource type. + /// The resource builder. + /// The host port. + /// The scheme e.g. (http/https) + /// The name of the binding. + /// The . + /// Throws an exception if the a binding with the same name already exists on the specified resource. + public static IResourceBuilder WithServiceBinding(this IResourceBuilder builder, int? hostPort = null, string? scheme = null, string? name = null) where T : IResource + { + if (builder.Resource.Annotations.OfType().Any(sb => sb.Name == name)) + { + throw new DistributedApplicationException($"Service binding with name '{name}' already exists"); + } + + var annotation = new ServiceBindingAnnotation(ProtocolType.Tcp, uriScheme: scheme, name: name, port: hostPort); + return builder.WithAnnotation(annotation); + } + + /// + /// Gets an by name from the resource. These endpoints are declared either using or by launch settings (for project resources). + /// The can be used to resolve the address of the endpoint in . + /// + /// The resource type. + /// The the resource builder. + /// The name of the service binding. + /// An that can be used to resolve the address of the endpoint after resource allocation has occurred. + public static EndpointReference GetEndpoint(this IResourceBuilder builder, string name) where T : IResourceWithBindings + { + return builder.Resource.GetEndpoint(name); + } + + /// + /// Configures a resource to mark all service binding's transport as HTTP/2. This is useful for HTTP/2 services that need prior knowledge. + /// + /// The resource type. + /// The resource builder. + /// The . + public static IResourceBuilder AsHttp2Service(this IResourceBuilder builder) where T : IResourceWithBindings + { + return builder.WithAnnotation(new Http2ServiceAnnotation()); + } + + /// + /// Excludes a resource from being published to the manifest. + /// + /// The resource type. + /// The resource to exclude. + /// The . + public static IResourceBuilder ExcludeFromManifest(this IResourceBuilder builder) where T : IResource + { + foreach (var annotation in builder.Resource.Annotations.OfType()) + { + builder.Resource.Annotations.Remove(annotation); + } + + return builder.WithAnnotation(ManifestPublishingCallbackAnnotation.Ignore); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Extensions/ResourceExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Extensions/ResourceExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Extensions/ResourceExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Extensions/ResourceExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,85 @@ +// 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.CodeAnalysis; + +namespace Aspire.Hosting.ApplicationModel; + +public static class ResourceExtensions +{ + public static bool TryGetLastAnnotation(this IResource resource, [NotNullWhen(true)] out T? annotation) where T : IResourceAnnotation + { + if (resource.Annotations.OfType().LastOrDefault() is { } lastAnnotation) + { + annotation = lastAnnotation; + return true; + } + else + { + annotation = default(T); + return false; + } + } + + public static bool TryGetAnnotationsOfType(this IResource resource, [NotNullWhen(true)] out IEnumerable? result) where T : IResourceAnnotation + { + var matchingTypeAnnotations = resource.Annotations.OfType(); + + if (matchingTypeAnnotations.Any()) + { + result = matchingTypeAnnotations.ToArray(); + return true; + } + else + { + result = null; + return false; + } + } + + public static bool TryGetEnvironmentVariables(this IResource resource, [NotNullWhen(true)] out IEnumerable? environmentVariables) + { + return TryGetAnnotationsOfType(resource, out environmentVariables); + } + + public static bool TryGetVolumeMounts(this IResource resource, [NotNullWhen(true)] out IEnumerable? volumeMounts) + { + return TryGetAnnotationsOfType(resource, out volumeMounts); + } + + public static bool TryGetServiceBindings(this IResource resource, [NotNullWhen(true)] out IEnumerable? serviceBindings) + { + return TryGetAnnotationsOfType(resource, out serviceBindings); + } + + public static bool TryGetAllocatedEndPoints(this IResource resource, [NotNullWhen(true)] out IEnumerable? allocatedEndPoints) + { + return TryGetAnnotationsOfType(resource, out allocatedEndPoints); + } + + public static bool TryGetContainerImageName(this IResource resource, [NotNullWhen(true)] out string? imageName) + { + if (resource.Annotations.OfType().LastOrDefault() is { } imageAnnotation) + { + var registryPrefix = string.IsNullOrEmpty(imageAnnotation.Registry) ? string.Empty : $"{imageAnnotation.Registry}/"; + var tagSuffix = string.IsNullOrEmpty(imageAnnotation.Tag) ? string.Empty : $":{imageAnnotation.Tag}"; + imageName = $"{registryPrefix}{imageAnnotation.Image}{tagSuffix}"; + return true; + } + + imageName = null; + return false; + } + + public static int GetReplicaCount(this IResource resource) + { + if (resource.TryGetLastAnnotation(out var replicaAnnotation)) + { + return replicaAnnotation.Replicas; + } + else + { + return 1; + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/IDistributedApplicationBuilder.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/IDistributedApplicationBuilder.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/IDistributedApplicationBuilder.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/IDistributedApplicationBuilder.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Aspire.Hosting; + +public interface IDistributedApplicationBuilder +{ + public ConfigurationManager Configuration { get; } + public IHostEnvironment Environment { get; } + public IServiceCollection Services { get; } + public IResourceCollection Resources { get; } + + /// + /// Adds a resource to the application. + /// + /// + IResourceBuilder AddResource(T resource) where T : IResource; + + DistributedApplication Build(); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/IServiceMetadata.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/IServiceMetadata.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/IServiceMetadata.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/IServiceMetadata.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +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 Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting; + +public interface IServiceMetadata : IResourceAnnotation +{ + public string AssemblyName { get; } + public string AssemblyPath { get; } + public string ProjectPath { get; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Lifecycle/IDistributedApplicationLifecycleHook.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Lifecycle/IDistributedApplicationLifecycleHook.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Lifecycle/IDistributedApplicationLifecycleHook.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Lifecycle/IDistributedApplicationLifecycleHook.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting.Lifecycle; + +public interface IDistributedApplicationLifecycleHook +{ + Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Lifecycle/LifecycleHookServiceCollectionExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Lifecycle/LifecycleHookServiceCollectionExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Lifecycle/LifecycleHookServiceCollectionExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Lifecycle/LifecycleHookServiceCollectionExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.DependencyInjection; + +namespace Aspire.Hosting.Lifecycle; + +public static class LifecycleHookServiceCollectionExtensions +{ + public static void AddLifecycleHook(this IServiceCollection services) where T : class, IDistributedApplicationLifecycleHook + { + services.AddSingleton(); + } + + public static void AddLifecycleHook(this IServiceCollection services, Func implementationFactory) where T : class, IDistributedApplicationLifecycleHook + { + services.AddSingleton(implementationFactory); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Otlp/OtlpConfigurationExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Otlp/OtlpConfigurationExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Otlp/OtlpConfigurationExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Otlp/OtlpConfigurationExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace Aspire.Hosting; + +public static class OtlpConfigurationExtensions +{ + private const string DashboardOtlpUrlVariableName = "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL"; + private const string DashboardOtlpUrlDefaultValue = "http://localhost:18889"; + + public static void AddOtlpEnvironment(IResource resource, IConfiguration configuration, IHostEnvironment environment) + { + // Configure OpenTelemetry in projects using environment variables. + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md + + resource.Annotations.Add(new EnvironmentCallbackAnnotation(context => + { + if (context.PublisherName == "manifest") + { + // REVIEW: Do we want to set references to an imaginary otlp provider as a requirement? + return; + } + + context.EnvironmentVariables["OTEL_EXPORTER_OTLP_ENDPOINT"] = configuration[DashboardOtlpUrlVariableName] ?? DashboardOtlpUrlDefaultValue; + + // Set the service name and instance id to the resource name and UID. Values are injected by DCP. + context.EnvironmentVariables["OTEL_RESOURCE_ATTRIBUTES"] = "service.instance.id={{- .UID -}}"; + context.EnvironmentVariables["OTEL_SERVICE_NAME"] = "{{- .Name -}}"; + + // Set a small batch schedule delay in development. + // This reduces the delay that OTLP exporter waits to sends telemetry and makes the dashboard telemetry pages responsive. + if (environment.IsDevelopment()) + { + var value = "1000"; // milliseconds + context.EnvironmentVariables["OTEL_BLRP_SCHEDULE_DELAY"] = value; + context.EnvironmentVariables["OTEL_BSP_SCHEDULE_DELAY"] = value; + context.EnvironmentVariables["OTEL_METRIC_EXPORT_INTERVAL"] = value; + } + })); + } + + /// + /// Injects the appropriate environment variables to allow the resource to enable sending telemetry to the dashboard. + /// 1. It sets the OTLP endpoint to the value of the DOTNET_DASHBOARD_OTLP_ENDPOINT_URL environment variable. + /// 2. It sets the service name and instance id to the resource name and UID. Values are injected by the orchestrator. + /// 3. It sets a small batch schedule delay in development. This reduces the delay that OTLP exporter waits to sends telemetry and makes the dashboard telemetry pages responsive. + /// + /// The resource type. + /// The resource builder. + /// The . + public static IResourceBuilder WithOtlpExporter(this IResourceBuilder builder) where T : IResourceWithEnvironment + { + AddOtlpEnvironment(builder.Resource, builder.ApplicationBuilder.Configuration, builder.ApplicationBuilder.Environment); + return builder; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Postgres/IPostgresResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Postgres/IPostgresResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Postgres/IPostgresResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Postgres/IPostgresResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public interface IPostgresResource : IResourceWithConnectionString +{ +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Postgres/PostgresBuilderExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Postgres/PostgresBuilderExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Postgres/PostgresBuilderExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Postgres/PostgresBuilderExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Sockets; +using System.Text.Json; +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting; + +public static class PostgresBuilderExtensions +{ + private const string PasswordEnvVarName = "POSTGRES_PASSWORD"; + + /// + /// Adds a PostgreSQL container to the application model. The default image is "postgres" and the tag is "latest". + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The host port for PostgreSQL. + /// The password for the PostgreSQL container. Defaults to a random password. + /// A reference to the . + public static IResourceBuilder AddPostgresContainer(this IDistributedApplicationBuilder builder, string name, int? port = null, string? password = null) + { + password = password ?? Guid.NewGuid().ToString("N"); + var postgresContainer = new PostgresContainerResource(name, password); + return builder.AddResource(postgresContainer) + .WithAnnotation(new ManifestPublishingCallbackAnnotation(WritePostgresContainerToManifest)) + .WithAnnotation(new ServiceBindingAnnotation(ProtocolType.Tcp, port: port, containerPort: 5432)) // Internal port is always 5432. + .WithAnnotation(new ContainerImageAnnotation { Image = "postgres", Tag = "latest" }) + .WithEnvironment("POSTGRES_HOST_AUTH_METHOD", "trust") + .WithEnvironment(PasswordEnvVarName, () => postgresContainer.Password); + } + + /// + /// Adds a PostgreSQL connection to the application model. Connection strings can also be read from the connection string section of the configuration using the name of the resource. + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The PostgreSQL connection string (optional). + /// A reference to the . + public static IResourceBuilder AddPostgresConnection(this IDistributedApplicationBuilder builder, string name, string? connectionString = null) + { + var postgresConnection = new PostgresConnectionResource(name, connectionString); + + return builder.AddResource(postgresConnection) + .WithAnnotation(new ManifestPublishingCallbackAnnotation((json) => WritePostgresConnectionToManifest(json, postgresConnection))); + } + + /// + /// Adds a PostgreSQL database to the application model. + /// + /// The PostgreSQL server resource builder. + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// A reference to the . + public static IResourceBuilder AddDatabase(this IResourceBuilder builder, string name) + { + var postgresDatabase = new PostgresDatabaseResource(name, builder.Resource); + return builder.ApplicationBuilder.AddResource(postgresDatabase) + .WithAnnotation(new ManifestPublishingCallbackAnnotation( + (json) => WritePostgresDatabaseToManifest(json, postgresDatabase))); + } + + private static void WritePostgresConnectionToManifest(Utf8JsonWriter jsonWriter, PostgresConnectionResource postgresConnection) + { + jsonWriter.WriteString("type", "postgres.connection.v1"); + jsonWriter.WriteString("connectionString", postgresConnection.GetConnectionString()); + } + + private static void WritePostgresContainerToManifest(Utf8JsonWriter jsonWriter) + { + jsonWriter.WriteString("type", "postgres.server.v1"); + } + + private static void WritePostgresDatabaseToManifest(Utf8JsonWriter json, PostgresDatabaseResource postgresDatabase) + { + json.WriteString("type", "postgres.database.v1"); + json.WriteString("parent", postgresDatabase.Parent.Name); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Postgres/PostgresConnectionResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Postgres/PostgresConnectionResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Postgres/PostgresConnectionResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Postgres/PostgresConnectionResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a PostgreSQL connection. +/// +/// The name of the resource. +/// The PostgreSQL connection string. +public class PostgresConnectionResource(string name, string? connectionString) : Resource(name), IPostgresResource +{ + private readonly string? _connectionString = connectionString; + + /// + /// Gets the connection string for the PostgreSQL server. + /// + /// The specified connection string. + public string? GetConnectionString() => _connectionString; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Postgres/PostgresContainerResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Postgres/PostgresContainerResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Postgres/PostgresContainerResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Postgres/PostgresContainerResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a PostgreSQL container. +/// +/// The name of the resource. +/// The PostgreSQL server password. +public class PostgresContainerResource(string name, string password) : ContainerResource(name), IPostgresResource +{ + public string Password { get; } = password; + + /// + /// Gets the connection string for the PostgreSQL server. + /// + /// A connection string for the PostgreSQL server in the form "Host=host;Port=port;Username=postgres;Password=password". + public string? GetConnectionString() + { + if (!this.TryGetAllocatedEndPoints(out var allocatedEndpoints)) + { + throw new DistributedApplicationException("Expected allocated endpoints!"); + } + + var allocatedEndpoint = allocatedEndpoints.Single(); // We should only have one endpoint for Postgres. + + var connectionString = $"Host={allocatedEndpoint.Address};Port={allocatedEndpoint.Port};Username=postgres;Password={Password};"; + return connectionString; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Postgres/PostgresDatabaseResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Postgres/PostgresDatabaseResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Postgres/PostgresDatabaseResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Postgres/PostgresDatabaseResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a PostgreSQL database. This is a child resource of a . +/// +/// The name of the resource. +/// The PostgreSQL server resource associated with this database. +public class PostgresDatabaseResource(string name, PostgresContainerResource postgresContainer) : Resource(name), IPostgresResource, IResourceWithParent +{ + public PostgresContainerResource Parent { get; } = postgresContainer; + + /// + /// Gets the connection string for the Postgres database. + /// + /// A connection string for the Postgres database. + public string? GetConnectionString() + { + if (Parent.GetConnectionString() is { } connectionString) + { + return $"{connectionString}Database={Name}"; + } + else + { + throw new DistributedApplicationException("Parent resource connection string was null."); + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/Resources.Designer.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/Resources.Designer.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/Resources.Designer.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/Resources.Designer.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,126 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Aspire.Hosting.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Aspire.Hosting.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Docker could not be found. The error from Docker invocation was: {0}. + /// + internal static string DockerPrerequisiteMissingExceptionMessage { + get { + return ResourceManager.GetString("DockerPrerequisiteMissingExceptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}.. + /// + internal static string DockerUnhealthyExceptionMessage { + get { + return ResourceManager.GetString("DockerUnhealthyExceptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds.. + /// + internal static string DockerUnresponsiveExceptionMessage { + get { + return ResourceManager.GetString("DockerUnresponsiveExceptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Launch profile is specified but launch settings file is not present.. + /// + internal static string LaunchProfileIsSpecifiedButLaunchSettingsFileIsNotPresentExceptionMessage { + get { + return ResourceManager.GetString("LaunchProfileIsSpecifiedButLaunchSettingsFileIsNotPresentExceptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Launch settings file does not contain '{0}' profile.. + /// + internal static string LaunchSettingsFileDoesNotContainProfileExceptionMessage { + get { + return ResourceManager.GetString("LaunchSettingsFileDoesNotContainProfileExceptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Project does not contain service metadata.. + /// + internal static string ProjectDoesNotContainServiceMetadataExceptionMessage { + get { + return ResourceManager.GetString("ProjectDoesNotContainServiceMetadataExceptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Project file '{0}' was not found.. + /// + internal static string ProjectFileNotFoundExceptionMessage { + get { + return ResourceManager.GetString("ProjectFileNotFoundExceptionMessage", resourceCulture); + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/Resources.resx dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/Resources.resx --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/Resources.resx 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/Resources.resx 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Docker could not be found. The error from Docker invocation was: {0} + + + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + + + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + + + Launch profile is specified but launch settings file is not present. + + + Launch settings file does not contain '{0}' profile. + + + Project does not contain service metadata. + + + Project file '{0}' was not found. + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.cs.xlf dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.cs.xlf --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.cs.xlf 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.cs.xlf 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,42 @@ + + + + + + Docker could not be found. The error from Docker invocation was: {0} + Docker could not be found. The error from Docker invocation was: {0} + + + + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + + + + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + + + + Launch profile is specified but launch settings file is not present. + Launch profile is specified but launch settings file is not present. + + + + Launch settings file does not contain '{0}' profile. + Launch settings file does not contain '{0}' profile. + + + + Project does not contain service metadata. + Project does not contain service metadata. + + + + Project file '{0}' was not found. + Project file '{0}' was not found. + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.de.xlf dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.de.xlf --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.de.xlf 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.de.xlf 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,42 @@ + + + + + + Docker could not be found. The error from Docker invocation was: {0} + Docker could not be found. The error from Docker invocation was: {0} + + + + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + + + + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + + + + Launch profile is specified but launch settings file is not present. + Launch profile is specified but launch settings file is not present. + + + + Launch settings file does not contain '{0}' profile. + Launch settings file does not contain '{0}' profile. + + + + Project does not contain service metadata. + Project does not contain service metadata. + + + + Project file '{0}' was not found. + Project file '{0}' was not found. + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.es.xlf dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.es.xlf --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.es.xlf 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.es.xlf 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,42 @@ + + + + + + Docker could not be found. The error from Docker invocation was: {0} + Docker could not be found. The error from Docker invocation was: {0} + + + + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + + + + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + + + + Launch profile is specified but launch settings file is not present. + Launch profile is specified but launch settings file is not present. + + + + Launch settings file does not contain '{0}' profile. + Launch settings file does not contain '{0}' profile. + + + + Project does not contain service metadata. + Project does not contain service metadata. + + + + Project file '{0}' was not found. + Project file '{0}' was not found. + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.fr.xlf dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.fr.xlf --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.fr.xlf 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.fr.xlf 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,42 @@ + + + + + + Docker could not be found. The error from Docker invocation was: {0} + Docker could not be found. The error from Docker invocation was: {0} + + + + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + + + + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + + + + Launch profile is specified but launch settings file is not present. + Launch profile is specified but launch settings file is not present. + + + + Launch settings file does not contain '{0}' profile. + Launch settings file does not contain '{0}' profile. + + + + Project does not contain service metadata. + Project does not contain service metadata. + + + + Project file '{0}' was not found. + Project file '{0}' was not found. + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.it.xlf dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.it.xlf --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.it.xlf 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.it.xlf 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,42 @@ + + + + + + Docker could not be found. The error from Docker invocation was: {0} + Docker could not be found. The error from Docker invocation was: {0} + + + + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + + + + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + + + + Launch profile is specified but launch settings file is not present. + Launch profile is specified but launch settings file is not present. + + + + Launch settings file does not contain '{0}' profile. + Launch settings file does not contain '{0}' profile. + + + + Project does not contain service metadata. + Project does not contain service metadata. + + + + Project file '{0}' was not found. + Project file '{0}' was not found. + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.ja.xlf dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.ja.xlf --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.ja.xlf 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.ja.xlf 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,42 @@ + + + + + + Docker could not be found. The error from Docker invocation was: {0} + Docker could not be found. The error from Docker invocation was: {0} + + + + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + + + + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + + + + Launch profile is specified but launch settings file is not present. + Launch profile is specified but launch settings file is not present. + + + + Launch settings file does not contain '{0}' profile. + Launch settings file does not contain '{0}' profile. + + + + Project does not contain service metadata. + Project does not contain service metadata. + + + + Project file '{0}' was not found. + Project file '{0}' was not found. + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.ko.xlf dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.ko.xlf --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.ko.xlf 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.ko.xlf 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,42 @@ + + + + + + Docker could not be found. The error from Docker invocation was: {0} + Docker could not be found. The error from Docker invocation was: {0} + + + + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + + + + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + + + + Launch profile is specified but launch settings file is not present. + Launch profile is specified but launch settings file is not present. + + + + Launch settings file does not contain '{0}' profile. + Launch settings file does not contain '{0}' profile. + + + + Project does not contain service metadata. + Project does not contain service metadata. + + + + Project file '{0}' was not found. + Project file '{0}' was not found. + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.pl.xlf dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.pl.xlf --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.pl.xlf 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.pl.xlf 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,42 @@ + + + + + + Docker could not be found. The error from Docker invocation was: {0} + Docker could not be found. The error from Docker invocation was: {0} + + + + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + + + + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + + + + Launch profile is specified but launch settings file is not present. + Launch profile is specified but launch settings file is not present. + + + + Launch settings file does not contain '{0}' profile. + Launch settings file does not contain '{0}' profile. + + + + Project does not contain service metadata. + Project does not contain service metadata. + + + + Project file '{0}' was not found. + Project file '{0}' was not found. + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.pt-BR.xlf dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.pt-BR.xlf --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.pt-BR.xlf 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.pt-BR.xlf 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,42 @@ + + + + + + Docker could not be found. The error from Docker invocation was: {0} + Docker could not be found. The error from Docker invocation was: {0} + + + + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + + + + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + + + + Launch profile is specified but launch settings file is not present. + Launch profile is specified but launch settings file is not present. + + + + Launch settings file does not contain '{0}' profile. + Launch settings file does not contain '{0}' profile. + + + + Project does not contain service metadata. + Project does not contain service metadata. + + + + Project file '{0}' was not found. + Project file '{0}' was not found. + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.ru.xlf dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.ru.xlf --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.ru.xlf 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.ru.xlf 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,42 @@ + + + + + + Docker could not be found. The error from Docker invocation was: {0} + Docker could not be found. The error from Docker invocation was: {0} + + + + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + + + + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + + + + Launch profile is specified but launch settings file is not present. + Launch profile is specified but launch settings file is not present. + + + + Launch settings file does not contain '{0}' profile. + Launch settings file does not contain '{0}' profile. + + + + Project does not contain service metadata. + Project does not contain service metadata. + + + + Project file '{0}' was not found. + Project file '{0}' was not found. + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.tr.xlf dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.tr.xlf --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.tr.xlf 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.tr.xlf 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,42 @@ + + + + + + Docker could not be found. The error from Docker invocation was: {0} + Docker could not be found. The error from Docker invocation was: {0} + + + + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + + + + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + + + + Launch profile is specified but launch settings file is not present. + Launch profile is specified but launch settings file is not present. + + + + Launch settings file does not contain '{0}' profile. + Launch settings file does not contain '{0}' profile. + + + + Project does not contain service metadata. + Project does not contain service metadata. + + + + Project file '{0}' was not found. + Project file '{0}' was not found. + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.zh-Hans.xlf dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.zh-Hans.xlf --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.zh-Hans.xlf 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.zh-Hans.xlf 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,42 @@ + + + + + + Docker could not be found. The error from Docker invocation was: {0} + Docker could not be found. The error from Docker invocation was: {0} + + + + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + + + + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + + + + Launch profile is specified but launch settings file is not present. + Launch profile is specified but launch settings file is not present. + + + + Launch settings file does not contain '{0}' profile. + Launch settings file does not contain '{0}' profile. + + + + Project does not contain service metadata. + Project does not contain service metadata. + + + + Project file '{0}' was not found. + Project file '{0}' was not found. + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.zh-Hant.xlf dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.zh-Hant.xlf --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.zh-Hant.xlf 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Properties/xlf/Resources.zh-Hant.xlf 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,42 @@ + + + + + + Docker could not be found. The error from Docker invocation was: {0} + Docker could not be found. The error from Docker invocation was: {0} + + + + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + Docker was found but appears to be unhealthy. Exit code for '{0}' was {1}. + + + + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + Docker was found but appears to be unresponsive. The command '{0}' did not return after {1} seconds. + + + + Launch profile is specified but launch settings file is not present. + Launch profile is specified but launch settings file is not present. + + + + Launch settings file does not contain '{0}' profile. + Launch settings file does not contain '{0}' profile. + + + + Project does not contain service metadata. + Project does not contain service metadata. + + + + Project file '{0}' was not found. + Project file '{0}' was not found. + + + + + \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Publishing/AutomaticManifestPublisherBindingInjectionHook.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Publishing/AutomaticManifestPublisherBindingInjectionHook.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Publishing/AutomaticManifestPublisherBindingInjectionHook.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Publishing/AutomaticManifestPublisherBindingInjectionHook.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Lifecycle; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; + +namespace Aspire.Hosting.Publishing; +internal sealed class AutomaticManifestPublisherBindingInjectionHook(IOptions publishingOptions) : IDistributedApplicationLifecycleHook +{ + private readonly IOptions _publishingOptions = publishingOptions; + + private static bool IsKestrelHttp2ConfigurationPresent(ProjectResource projectResource) + { + var serviceMetadata = projectResource.GetServiceMetadata(); + + var projectDirectoryPath = Path.GetDirectoryName(serviceMetadata.ProjectPath)!; + var appSettingsPath = Path.Combine(projectDirectoryPath, "appsettings.json"); + var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT"); + var appSettingsEnvironmentPath = Path.Combine(projectDirectoryPath, $"appsettings.{env}.json"); + + var configBuilder = new ConfigurationBuilder(); + configBuilder.AddJsonFile(appSettingsPath, optional: true); + configBuilder.AddJsonFile(appSettingsEnvironmentPath, optional: true); + var config = configBuilder.Build(); + var protocol = config["Kestrel:EndpointDefaults:Protocols"]; + return protocol == "Http2"; + } + + public Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default) + { + if (_publishingOptions.Value.Publisher != "manifest") + { + return Task.CompletedTask; + } + + var projectResources = appModel.Resources.OfType(); + + foreach (var projectResource in projectResources) + { + var isHttp2ConfiguredInAppSettings = IsKestrelHttp2ConfigurationPresent(projectResource); + + if (!projectResource.Annotations.OfType().Any(sb => sb.UriScheme == "http" || sb.Name == "http")) + { + var httpBinding = new ServiceBindingAnnotation( + System.Net.Sockets.ProtocolType.Tcp, + uriScheme: "http" + ); + projectResource.Annotations.Add(httpBinding); + httpBinding.Transport = isHttp2ConfiguredInAppSettings ? "http2" : httpBinding.Transport; + } + + if (!projectResource.Annotations.OfType().Any(sb => sb.UriScheme == "https" || sb.Name == "https")) + { + var httpsBinding = new ServiceBindingAnnotation( + System.Net.Sockets.ProtocolType.Tcp, + uriScheme: "https" + ); + projectResource.Annotations.Add(httpsBinding); + httpsBinding.Transport = isHttp2ConfiguredInAppSettings ? "http2" : httpsBinding.Transport; + } + } + + return Task.CompletedTask; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Publishing/DcpPublisher.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Publishing/DcpPublisher.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Publishing/DcpPublisher.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Publishing/DcpPublisher.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting.Publishing; + +internal sealed class DcpPublisher : IDistributedApplicationPublisher +{ + public Task PublishAsync(DistributedApplicationModel model, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Publishing/Http2TransportMutationHook.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Publishing/Http2TransportMutationHook.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Publishing/Http2TransportMutationHook.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Publishing/Http2TransportMutationHook.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Lifecycle; + +namespace Aspire.Hosting.Publishing; + +internal sealed class Http2TransportMutationHook : IDistributedApplicationLifecycleHook +{ + public Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default) + { + foreach (var resource in appModel.Resources) + { + var isHttp2Service = resource.Annotations.OfType().Any(); + var httpBindings = resource.Annotations.OfType().Where(sb => sb.UriScheme == "http" || sb.UriScheme == "https"); + foreach (var httpBinding in httpBindings) + { + httpBinding.Transport = isHttp2Service ? "http2" : httpBinding.Transport; + } + } + + return Task.CompletedTask; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Publishing/IDistributedApplicationPublisher.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Publishing/IDistributedApplicationPublisher.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Publishing/IDistributedApplicationPublisher.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Publishing/IDistributedApplicationPublisher.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting.Publishing; + +public interface IDistributedApplicationPublisher +{ + public Task PublishAsync(DistributedApplicationModel model, CancellationToken cancellationToken); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Publishing/ManifestPublisher.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Publishing/ManifestPublisher.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Publishing/ManifestPublisher.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Publishing/ManifestPublisher.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,211 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; +using Aspire.Hosting.ApplicationModel; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; + +namespace Aspire.Hosting.Publishing; + +internal sealed class ManifestPublisher(IOptions options, IHostApplicationLifetime lifetime) : IDistributedApplicationPublisher +{ + private readonly IOptions _options = options; + private readonly IHostApplicationLifetime _lifetime = lifetime; + + public async Task PublishAsync(DistributedApplicationModel model, CancellationToken cancellationToken) + { + await WriteManifestAsync(model, cancellationToken).ConfigureAwait(false); + _lifetime.StopApplication(); + } + + private async Task WriteManifestAsync(DistributedApplicationModel model, CancellationToken cancellationToken) + { + if (_options.Value.OutputPath == null) + { + throw new DistributedApplicationException( + "The '--output-path [path]' option was not specified even though '--publish manifest' argument was used." + ); + } + + using var stream = new FileStream(_options.Value.OutputPath, FileMode.Create); + using var jsonWriter = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }); + + jsonWriter.WriteStartObject(); + WriteResources(model, jsonWriter); + jsonWriter.WriteEndObject(); + + await jsonWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + } + + private void WriteResources(DistributedApplicationModel model, Utf8JsonWriter jsonWriter) + { + jsonWriter.WriteStartObject("resources"); + foreach (var resource in model.Resources) + { + WriteResource(resource, jsonWriter); + } + jsonWriter.WriteEndObject(); + } + + private void WriteResource(IResource resource, Utf8JsonWriter jsonWriter) + { + // First see if the resource has a callback annotation with overrides the behavior for rendering + // out the JSON. If so use that callback, otherwise use the fallback logic that we have. + if (resource.TryGetLastAnnotation(out var manifestPublishingCallbackAnnotation)) + { + if (manifestPublishingCallbackAnnotation.Callback != null) + { + WriteResourceObject(resource, () => manifestPublishingCallbackAnnotation.Callback(jsonWriter)); + } + } + else if (resource is ContainerResource container) + { + WriteResourceObject(container, () => WriteContainer(container, jsonWriter)); + } + else if (resource is ProjectResource project) + { + WriteResourceObject(project, () => WriteProject(project, jsonWriter)); + } + else if (resource is ExecutableResource executable) + { + WriteResourceObject(executable, () => WriteExecutable(executable, jsonWriter)); + } + else + { + WriteResourceObject(resource, () => WriteError(jsonWriter)); + } + + void WriteResourceObject(T resource, Action action) where T: IResource + { + jsonWriter.WriteStartObject(resource.Name); + action(); + jsonWriter.WriteEndObject(); + } + } + + private static void WriteError(Utf8JsonWriter jsonWriter) + { + jsonWriter.WriteString("error", "This resource does not support generation in the manifest."); + } + + private static void WriteServiceDiscoveryEnvironmentVariables(IResource resource, Utf8JsonWriter jsonWriter) + { + var serviceReferenceAnnotations = resource.Annotations.OfType(); + + if (serviceReferenceAnnotations.Any()) + { + foreach (var serviceReferenceAnnotation in serviceReferenceAnnotations) + { + var bindingNames = serviceReferenceAnnotation.UseAllBindings + ? serviceReferenceAnnotation.Resource.Annotations.OfType().Select(sb => sb.Name) + : serviceReferenceAnnotation.BindingNames; + + var serviceBindingAnnotationsGroupedByScheme = serviceReferenceAnnotation.Resource.Annotations + .OfType() + .Where(sba => bindingNames.Contains(sba.Name)) + .GroupBy(sba => sba.UriScheme); + + var i = 0; + foreach (var serviceBindingAnnotationGroupedByScheme in serviceBindingAnnotationsGroupedByScheme) + { + // HACK: For November we are only going to support a single service binding annotation + // per URI scheme per service reference. + var binding = serviceBindingAnnotationGroupedByScheme.Single(); + + jsonWriter.WriteString($"services__{serviceReferenceAnnotation.Resource.Name}__{i++}", $"{{{serviceReferenceAnnotation.Resource.Name}.bindings.{binding.Name}.url}}"); + } + + } + } + } + + private static void WriteEnvironmentVariables(IResource resource, Utf8JsonWriter jsonWriter) + { + var config = new Dictionary(); + var context = new EnvironmentCallbackContext("manifest", config); + + if (resource.TryGetAnnotationsOfType(out var callbacks)) + { + jsonWriter.WriteStartObject("env"); + foreach (var callback in callbacks) + { + callback.Callback(context); + } + + foreach (var (key, value) in config) + { + jsonWriter.WriteString(key, value); + } + + WriteServiceDiscoveryEnvironmentVariables(resource, jsonWriter); + + jsonWriter.WriteEndObject(); + } + } + + private static void WriteBindings(IResource resource, Utf8JsonWriter jsonWriter) + { + if (resource.TryGetServiceBindings(out var serviceBindings)) + { + jsonWriter.WriteStartObject("bindings"); + foreach (var serviceBinding in serviceBindings) + { + jsonWriter.WriteStartObject(serviceBinding.Name); + jsonWriter.WriteString("scheme", serviceBinding.UriScheme); + jsonWriter.WriteString("protocol", serviceBinding.Protocol.ToString().ToLowerInvariant()); + jsonWriter.WriteString("transport", serviceBinding.Transport); + + if (serviceBinding.IsExternal) + { + jsonWriter.WriteBoolean("external", serviceBinding.IsExternal); + } + + jsonWriter.WriteEndObject(); + } + jsonWriter.WriteEndObject(); + } + } + + private static void WriteContainer(ContainerResource container, Utf8JsonWriter jsonWriter) + { + jsonWriter.WriteString("type", "container.v1"); + + if (!container.TryGetContainerImageName(out var image)) + { + throw new DistributedApplicationException("Could not get container image name."); + } + + jsonWriter.WriteString("image", image); + + WriteEnvironmentVariables(container, jsonWriter); + WriteBindings(container, jsonWriter); + } + + private void WriteProject(ProjectResource project, Utf8JsonWriter jsonWriter) + { + jsonWriter.WriteString("type", "project.v1"); + + if (!project.TryGetLastAnnotation(out var metadata)) + { + throw new DistributedApplicationException("Service metadata not found."); + } + + var manifestPath = _options.Value.OutputPath ?? throw new DistributedApplicationException("Output path not specified"); + var fullyQualifiedManifestPath = Path.GetFullPath(manifestPath); + var manifestDirectory = Path.GetDirectoryName(fullyQualifiedManifestPath) ?? throw new DistributedApplicationException("Could not get directory name of output path"); + var relativePathToProjectFile = Path.GetRelativePath(manifestDirectory, metadata.ProjectPath); + jsonWriter.WriteString("path", relativePathToProjectFile); + + WriteEnvironmentVariables(project, jsonWriter); + WriteBindings(project, jsonWriter); + } + + private static void WriteExecutable(ExecutableResource executable, Utf8JsonWriter jsonWriter) + { + jsonWriter.WriteString("type", "executable.v1"); + + WriteEnvironmentVariables(executable, jsonWriter); + WriteBindings(executable, jsonWriter); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Publishing/PublishingOptions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Publishing/PublishingOptions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Publishing/PublishingOptions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Publishing/PublishingOptions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.Publishing; + +public sealed class PublishingOptions +{ + public const string Publishing = "Publishing"; + + public string? Publisher { get; set; } + public string? OutputPath { get; set; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/RabbitMQ/RabbitMQBuilderExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/RabbitMQ/RabbitMQBuilderExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/RabbitMQ/RabbitMQBuilderExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/RabbitMQ/RabbitMQBuilderExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Sockets; +using System.Text.Json; +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting; + +public static class RabbitMQBuilderExtensions +{ + /// + /// Adds a RabbitMQ container to the application. The default image name is "rabbitmq" and the default tag is "3-management". + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The host port of RabbitMQ. + /// The password for RabbitMQ. The default is "guest". + /// A reference to the . + public static IResourceBuilder AddRabbitMQContainer(this IDistributedApplicationBuilder builder, string name, int? port = null, string? password = null) + { + var rabbitMq = new RabbitMQContainerResource(name, password); + return builder.AddResource(rabbitMq) + .WithAnnotation(new ServiceBindingAnnotation(ProtocolType.Tcp, port: port, containerPort: 5672)) + .WithAnnotation(new ServiceBindingAnnotation(ProtocolType.Tcp, uriScheme: "http", name: "management", port: null, containerPort: 15672)) + .WithAnnotation(new ContainerImageAnnotation { Image = "rabbitmq", Tag = "3-management" }) + .WithAnnotation(new ManifestPublishingCallbackAnnotation(WriteRabbitMQContainerToManifest)) + .WithEnvironment("RABBITMQ_DEFAULT_USER", "guest") + .WithEnvironment("RABBITMQ_DEFAULT_PASS", () => rabbitMq.Password ?? "guest"); + } + + /// + /// Adds a RabbitMQ connection resource to the application. + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// A RabbitMQ connection string. + /// A reference to the . + public static IResourceBuilder AddRabbitMQConnection(this IDistributedApplicationBuilder builder, string name, string? connectionString = null) + { + var rabbitMqConnection = new RabbitMQConnectionResource(name, connectionString); + + return builder.AddResource(rabbitMqConnection) + .WithAnnotation(new ManifestPublishingCallbackAnnotation((json) => WriteRabbitMQConnectionToManifest(json, rabbitMqConnection))); + } + private static void WriteRabbitMQContainerToManifest(Utf8JsonWriter json) + { + json.WriteString("type", "rabbitmq.server.v1"); + } + + private static void WriteRabbitMQConnectionToManifest(Utf8JsonWriter json, RabbitMQConnectionResource rabbitMqConnection) + { + json.WriteString("type", "rabbitmq.connection.v1"); + json.WriteString("connectionString", rabbitMqConnection.GetConnectionString()); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/RabbitMQ/RabbitMQConnectionResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/RabbitMQ/RabbitMQConnectionResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/RabbitMQ/RabbitMQConnectionResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/RabbitMQ/RabbitMQConnectionResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a RabbitMQ connection. +/// +/// The name of the resource. +/// The connection string. +public class RabbitMQConnectionResource(string name, string? connectionString = null) : Resource(name), IResourceWithConnectionString +{ + public string? ConnectionString { get; set; } = connectionString; + + public string? GetConnectionString() => ConnectionString; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/RabbitMQ/RabbitMQContainerResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/RabbitMQ/RabbitMQContainerResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/RabbitMQ/RabbitMQContainerResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/RabbitMQ/RabbitMQContainerResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a RabbitMQ container. +/// +/// The name of the resource. +/// The RabbitMQ server password. +public class RabbitMQContainerResource(string name, string? password) : ContainerResource(name), IResourceWithConnectionString +{ + /// + /// The RabbitMQ server password. + /// + public string? Password { get; } = password; + + /// + /// Gets the connection string for the RabbitMQ server. + /// + /// A connection string for the RabbitMQ server in the form "amqp://user:password@host:port". + public string? GetConnectionString() + { + if (!this.TryGetAllocatedEndPoints(out var allocatedEndpoints)) + { + throw new DistributedApplicationException($"RabbitMQ resource \"{Name}\" does not have endpoint annotation."); + } + + var endpoint = allocatedEndpoints.Where(a => a.Name != "management").Single(); + if (Password is null) + { + return $"amqp://{endpoint.EndPointString}"; + } + + return $"amqp://guest:{Password}@{endpoint.EndPointString}"; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Redis/IRedisResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Redis/IRedisResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Redis/IRedisResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Redis/IRedisResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public interface IRedisResource : IResourceWithConnectionString +{ +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Redis/RedisBuilderExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Redis/RedisBuilderExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Redis/RedisBuilderExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Redis/RedisBuilderExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Sockets; +using System.Text.Json; +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting; + +public static class RedisBuilderExtensions +{ + /// + /// Adds a Redis container to the application model. The default image is "redis" and tag is "latest". + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The host port for the redis server. + /// A reference to the . + public static IResourceBuilder AddRedisContainer(this IDistributedApplicationBuilder builder, string name, int? port = null) + { + var redis = new RedisContainerResource(name); + return builder.AddResource(redis) + .WithAnnotation(new ManifestPublishingCallbackAnnotation(WriteRedisResourceToManifest)) + .WithAnnotation(new ServiceBindingAnnotation(ProtocolType.Tcp, port: port, containerPort: 6379)) + .WithAnnotation(new ContainerImageAnnotation { Image = "redis", Tag = "latest" }); + } + + /// + /// Adds a Redis connection to the application model. Connection strings can also be read from the connection string section of the configuration using the name of the resource. + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The connection string. + /// A reference to the . + public static IResourceBuilder AddRedis(this IDistributedApplicationBuilder builder, string name, string? connectionString = null) + { + var redis = new RedisResource(name, connectionString); + return builder.AddResource(redis) + .WithAnnotation(new ManifestPublishingCallbackAnnotation(jsonWriter => + WriteRedisResourceToManifest(jsonWriter, redis.GetConnectionString()))); + } + + private static void WriteRedisResourceToManifest(Utf8JsonWriter jsonWriter) => + WriteRedisResourceToManifest(jsonWriter, null); + + private static void WriteRedisResourceToManifest(Utf8JsonWriter jsonWriter, string? connectionString) + { + jsonWriter.WriteString("type", "redis.v1"); + if (!string.IsNullOrEmpty(connectionString)) + { + jsonWriter.WriteString("connectionString", connectionString); + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Redis/RedisContainerResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Redis/RedisContainerResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Redis/RedisContainerResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Redis/RedisContainerResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a Redis container. +/// +/// The name of the resource. +public class RedisContainerResource(string name) : ContainerResource(name), IRedisResource +{ + /// + /// Gets the connection string for the Redis server. + /// + /// A connection string for the redis server in the form "host:port". + public string GetConnectionString() + { + if (!this.TryGetAnnotationsOfType(out var allocatedEndpoints)) + { + throw new DistributedApplicationException("Redis resource does not have endpoint annotation."); + } + + // We should only have one endpoint for Redis for local scenarios. + var endpoint = allocatedEndpoints.Single(); + return endpoint.EndPointString; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Redis/RedisResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Redis/RedisResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Redis/RedisResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Redis/RedisResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a Redis connection. +/// +/// The name of the resource. +/// The connection string for the resource. +public class RedisResource(string name, string? connectionString) : Resource(name), IRedisResource +{ + /// + /// Gets the connection string for the Redis server. + /// + /// The specified connection string. + public string? GetConnectionString() => connectionString; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/SqlServer/ISqlServerResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/SqlServer/ISqlServerResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/SqlServer/ISqlServerResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/SqlServer/ISqlServerResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public interface ISqlServerResource : IResourceWithConnectionString +{ +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerBuilderExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerBuilderExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerBuilderExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerBuilderExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Sockets; +using System.Text.Json; +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting; + +public static class SqlServerBuilderExtensions +{ + /// + /// Adds a SQL Server container to the application model. The default image in "mcr.microsoft.com/mssql/server" an the tag is "2022-latest". + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The password of the SQL Server. By default, this will be randomly generated. + /// The host port for the SQL Server. + /// A reference to the . + public static IResourceBuilder AddSqlServerContainer(this IDistributedApplicationBuilder builder, string name, string? password = null, int? port = null) + { + password = password ?? Guid.NewGuid().ToString("N"); + var sqlServer = new SqlServerContainerResource(name, password); + + return builder.AddResource(sqlServer) + .WithAnnotation(new ManifestPublishingCallbackAnnotation(WriteSqlServerContainerToManifest)) + .WithAnnotation(new ServiceBindingAnnotation(ProtocolType.Tcp, port: port, containerPort: 1433)) + .WithAnnotation(new ContainerImageAnnotation { Registry = "mcr.microsoft.com", Image = "mssql/server", Tag = "2022-latest" }) + .WithEnvironment("ACCEPT_EULA", "Y") + .WithEnvironment("MSSQL_SA_PASSWORD", sqlServer.Password); + } + + /// + /// Adds a SQL Server connection to the application model. Connection strings can also be read from the connection string section of the configuration using the name of the resource. + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The connection string. + /// A reference to the . + public static IResourceBuilder AddSqlServerConnection(this IDistributedApplicationBuilder builder, string name, string? connectionString = null) + { + var sqlServerConnection = new SqlServerConnectionResource(name, connectionString); + + return builder.AddResource(sqlServerConnection) + .WithAnnotation(new ManifestPublishingCallbackAnnotation(jsonWriter => WriteSqlServerConnectionToManifest(jsonWriter, sqlServerConnection))); + } + + private static void WriteSqlServerConnectionToManifest(Utf8JsonWriter jsonWriter, SqlServerConnectionResource sqlServerConnection) + { + jsonWriter.WriteString("type", "sqlserver.connection.v1"); + jsonWriter.WriteString("connectionString", sqlServerConnection.GetConnectionString()); + } + + private static void WriteSqlServerContainerToManifest(Utf8JsonWriter jsonWriter) + { + jsonWriter.WriteString("type", "sqlserver.server.v1"); + } + + private static void WriteSqlServerDatabaseToManifest(Utf8JsonWriter json, SqlServerDatabaseResource sqlServerDatabase) + { + json.WriteString("type", "sqlserver.database.v1"); + json.WriteString("parent", sqlServerDatabase.Parent.Name); + } + + /// + /// Adds a SQL Server database to the application model. This is a child resource of a . + /// + /// The SQL Server resource builders. + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// A reference to the . + public static IResourceBuilder AddDatabase(this IResourceBuilder builder, string name) + { + var sqlServerDatabase = new SqlServerDatabaseResource(name, builder.Resource); + return builder.ApplicationBuilder.AddResource(sqlServerDatabase) + .WithAnnotation(new ManifestPublishingCallbackAnnotation( + (json) => WriteSqlServerDatabaseToManifest(json, sqlServerDatabase))); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerConnectionResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerConnectionResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerConnectionResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerConnectionResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a SQL Server connection. +/// +/// The name of the resource. +/// The SQL Server connection string. +public class SqlServerConnectionResource(string name, string? connectionString) : Resource(name), ISqlServerResource +{ + private readonly string? _connectionString = connectionString; + + /// + /// Gets the connection string for the SQL Server. + /// + /// The specified connection string. + public string? GetConnectionString() => _connectionString; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerContainerResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerContainerResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerContainerResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerContainerResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a SQL Server container. +/// +/// The name of the resource. +/// The SQL Sever password. +public class SqlServerContainerResource(string name, string password) : ContainerResource(name), ISqlServerResource +{ + public string Password { get; } = password; + + /// + /// Gets the connection string for the SQL Server. + /// + /// A connection string for the SQL Server in the form "Server=host,port;User ID=sa;Password=password;TrustServerCertificate=true;". + public string? GetConnectionString() + { + if (!this.TryGetAnnotationsOfType(out var allocatedEndpoints)) + { + throw new DistributedApplicationException("Expected allocated endpoints!"); + } + + var endpoint = allocatedEndpoints.Single(); + + // HACK: Use the 127.0.0.1 address because localhost is resolving to [::1] following + // up with DCP on this issue. + return $"Server=127.0.0.1,{endpoint.Port};User ID=sa;Password={Password};TrustServerCertificate=true;"; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerDatabaseResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerDatabaseResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerDatabaseResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/SqlServer/SqlServerDatabaseResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public class SqlServerDatabaseResource(string name, SqlServerContainerResource sqlServerContainer) : ContainerResource(name), ISqlServerResource, IResourceWithParent +{ + public SqlServerContainerResource Parent { get; } = sqlServerContainer; + + public string? GetConnectionString() + { + if (Parent.GetConnectionString() is { } connectionString) + { + return $"{connectionString}Database={Name}"; + } + else + { + throw new DistributedApplicationException("Parent resource connection string was null."); + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Utils/FileNameSuffixes.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Utils/FileNameSuffixes.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Utils/FileNameSuffixes.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Utils/FileNameSuffixes.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace Aspire.Hosting.Utils; + +public static class FileNameSuffixes +{ + public const string DepsJson = ".deps.json"; + public const string RuntimeConfigJson = ".runtimeconfig.json"; + public const string RuntimeConfigDevJson = ".runtimeconfig.dev.json"; + + public static PlatformFileNameSuffixes CurrentPlatform + { + get + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Windows; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return OSX; + } + else + { + // assume everything else is Unix to avoid modifying this file + // everytime a new platform is introduced in runtime. + return Unix; + } + } + } + + public static PlatformFileNameSuffixes DotNet { get; } = new PlatformFileNameSuffixes + { + DynamicLib = ".dll", + Exe = ".exe", + ProgramDatabase = ".pdb", + StaticLib = ".lib" + }; + + public static PlatformFileNameSuffixes Windows { get; } = new PlatformFileNameSuffixes + { + DynamicLib = ".dll", + Exe = ".exe", + ProgramDatabase = ".pdb", + StaticLib = ".lib" + }; + + public static PlatformFileNameSuffixes OSX { get; } = new PlatformFileNameSuffixes + { + DynamicLib = ".dylib", + Exe = string.Empty, + ProgramDatabase = ".pdb", + StaticLib = ".a" + }; + + public static PlatformFileNameSuffixes Unix { get; } = new PlatformFileNameSuffixes + { + DynamicLib = ".so", + Exe = string.Empty, + ProgramDatabase = ".pdb", + StaticLib = ".a" + }; + + public struct PlatformFileNameSuffixes + { + public string DynamicLib { get; internal set; } + + public string Exe { get; internal set; } + + public string ProgramDatabase { get; internal set; } + + public string StaticLib { get; internal set; } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Utils/FileUtil.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Utils/FileUtil.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Utils/FileUtil.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Utils/FileUtil.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.Utils; + +internal static class FileUtil +{ + public static string FindFullPathFromPath(string command) + { + ArgumentOutOfRangeException.ThrowIfNullOrWhiteSpace(command, nameof(command)); + + if (FileNameSuffixes.CurrentPlatform.Exe != "" && !command.EndsWith(FileNameSuffixes.CurrentPlatform.Exe)) + { + command = command + FileNameSuffixes.CurrentPlatform.Exe; + } + + foreach (var directory in (Environment.GetEnvironmentVariable("PATH") ?? string.Empty).Split(Path.PathSeparator)) + { + var fullPath = Path.Combine(directory, command + FileNameSuffixes.CurrentPlatform.Exe); + if (File.Exists(fullPath)) + { + return fullPath; + } + } + + return command; + } + +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Utils/LaunchProfile.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Utils/LaunchProfile.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Utils/LaunchProfile.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Utils/LaunchProfile.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization; + +namespace Aspire.Hosting; + +internal sealed class LaunchProfile +{ + [JsonPropertyName("commandName")] + public string? CommandName { get; set; } + + [JsonPropertyName("commandLineArgs")] + public string? CommandLineArgs { get; set; } + + [JsonPropertyName("dotnetRunMessages")] + public bool? DotnetRunMessages { get; set; } + + [JsonPropertyName("launchBrowser")] + public bool? LaunchBrowser { get; set; } + + [JsonPropertyName("launchUrl")] + public string? LaunchUrl { get; set; } + + [JsonPropertyName("applicationUrl")] + public string? ApplicationUrl { get; set; } + + [JsonPropertyName("environmentVariables")] + public Dictionary EnvironmentVariables { get; set; } = new Dictionary(); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Utils/LaunchProfileExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Utils/LaunchProfileExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Utils/LaunchProfileExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Utils/LaunchProfileExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,151 @@ +// 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.CodeAnalysis; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Properties; + +namespace Aspire.Hosting; + +internal static class LaunchProfileExtensions +{ + internal static LaunchSettings? GetLaunchSettings(this ProjectResource projectResource) + { + if (!projectResource.TryGetLastAnnotation(out var serviceMetadata)) + { + throw new DistributedApplicationException(Resources.ProjectDoesNotContainServiceMetadataExceptionMessage); + } + + return serviceMetadata.GetLaunchSettings(); + } + + internal static LaunchProfile? GetEffectiveLaunchProfile(this ProjectResource projectResource) + { + string? launchProfileName = projectResource.SelectLaunchProfileName(); + if (string.IsNullOrEmpty(launchProfileName)) + { + return null; + } + + var profiles = projectResource.GetLaunchSettings()?.Profiles; + if (profiles is null) + { + return null; + } + + var found = profiles.TryGetValue(launchProfileName, out var launchProfile); + return found == true ? launchProfile : null; + } + + internal static LaunchSettings? GetLaunchSettings(this IServiceMetadata serviceMetadata) + { + if (!File.Exists(serviceMetadata.ProjectPath)) + { + var message = string.Format(CultureInfo.InvariantCulture, Resources.ProjectFileNotFoundExceptionMessage, serviceMetadata.ProjectPath); + throw new DistributedApplicationException(message); + } + + var projectFileInfo = new FileInfo(serviceMetadata.ProjectPath); + var launchSettingsFilePath = projectFileInfo.DirectoryName switch + { + null => Path.Combine("Properties", "launchSettings.json"), + _ => Path.Combine(projectFileInfo.DirectoryName, "Properties", "launchSettings.json") + }; + + // It isn't mandatory that the launchSettings.json file exists! + if (!File.Exists(launchSettingsFilePath)) + { + return null; + } + + using var stream = File.OpenRead(launchSettingsFilePath); + var settings = JsonSerializer.Deserialize(stream, LaunchSetttingsSerializerContext.Default.LaunchSettings); + return settings; + } + + private static readonly LaunchProfileSelector[] s_launchProfileSelectors = + [ + TrySelectLaunchProfileFromAnnotation, + TrySelectLaunchProfileFromEnvironment, + TrySelectLaunchProfileByOrder + ]; + + private static bool TrySelectLaunchProfileByOrder(ProjectResource projectResource, [NotNullWhen(true)] out string? launchProfileName) + { + var launchSettings = GetLaunchSettings(projectResource); + + if (launchSettings == null || launchSettings.Profiles.Count == 0) + { + launchProfileName = null; + return false; + } + + launchProfileName = launchSettings.Profiles.Keys.First(); + return true; + } + + private static bool TrySelectLaunchProfileFromEnvironment(ProjectResource projectResource, [NotNullWhen(true)] out string? launchProfileName) + { + var launchProfileEnvironmentVariable = Environment.GetEnvironmentVariable("DOTNET_LAUNCH_PROFILE"); + + if (launchProfileEnvironmentVariable is null) + { + launchProfileName = null; + return false; + } + + var launchSettings = GetLaunchSettings(projectResource); + if (launchSettings == null) + { + launchProfileName = null; + return false; + } + + if (!launchSettings.Profiles.TryGetValue(launchProfileEnvironmentVariable, out var launchProfile)) + { + launchProfileName = null; + return false; + } + + launchProfileName = launchProfileEnvironmentVariable; + return launchProfile != null; + } + + private static bool TrySelectLaunchProfileFromAnnotation(ProjectResource projectResource, [NotNullWhen(true)] out string? launchProfileName) + { + if (projectResource.TryGetLastAnnotation(out var launchProfileAnnotation)) + { + launchProfileName = launchProfileAnnotation.LaunchProfileName; + return true; + } + else + { + launchProfileName = null; + return false; + } + } + + internal static string? SelectLaunchProfileName(this ProjectResource projectResource) + { + foreach (var launchProfileSelector in s_launchProfileSelectors) + { + if (launchProfileSelector(projectResource, out var launchProfile)) + { + return launchProfile; + } + } + + return null; + } +} + +internal delegate bool LaunchProfileSelector(ProjectResource project, out string? launchProfile); + +[JsonSerializable(typeof(LaunchSettings))] +internal sealed partial class LaunchSetttingsSerializerContext : JsonSerializerContext +{ + +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Utils/LaunchSettings.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Utils/LaunchSettings.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting/Utils/LaunchSettings.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting/Utils/LaunchSettings.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization; + +namespace Aspire.Hosting; + +internal sealed class LaunchSettings +{ + [JsonPropertyName("profiles")] + public Dictionary Profiles { get; set; } = new Dictionary(); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,12 @@ + + + + $(NetCurrent) + true + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure/AzureKeyVaultResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure/AzureKeyVaultResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure/AzureKeyVaultResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure/AzureKeyVaultResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public class AzureKeyVaultResource(string name) : Resource(name), IAzureResource, IResourceWithConnectionString +{ + public Uri? VaultUri { get; set; } + + public string? GetConnectionString() => VaultUri?.ToString(); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure/AzureRedisResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure/AzureRedisResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure/AzureRedisResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure/AzureRedisResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public class AzureRedisResource(string name) : Resource(name), IAzureResource, IResourceWithConnectionString +{ + public string? ConnectionString { get; set; } + + public string? GetConnectionString() => ConnectionString; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure/AzureResourceExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure/AzureResourceExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure/AzureResourceExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure/AzureResourceExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,183 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Sockets; +using System.Text.Json; +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting; + +public static class AzureResourceExtensions +{ + /// + /// Adds an Azure Key Vault resource to the application model. + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// A reference to the . + public static IResourceBuilder AddAzureKeyVault(this IDistributedApplicationBuilder builder, string name) + { + var keyVault = new AzureKeyVaultResource(name); + return builder.AddResource(keyVault) + .WithAnnotation(new ManifestPublishingCallbackAnnotation(WriteAzureKeyVaultToManifest)); + } + + private static void WriteAzureKeyVaultToManifest(Utf8JsonWriter jsonWriter) + { + jsonWriter.WriteString("type", "azure.keyvault.v1"); + } + + /// + /// Adds an Azure Service Bus resource to the application model. + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// A list of queue names associated with this service bus resource. + /// A list of topic names associated with this service bus resource. + /// A reference to the . + public static IResourceBuilder AddAzureServiceBus(this IDistributedApplicationBuilder builder, string name, string[]? queueNames = null, string[]? topicNames = null) + { + var resource = new AzureServiceBusResource(name) + { + QueueNames = queueNames ?? [], + TopicNames = topicNames ?? [] + }; + + return builder.AddResource(resource) + .WithAnnotation(new ManifestPublishingCallbackAnnotation(jsonWriter => WriteAzureServiceBusToManifest(resource, jsonWriter))); + } + + private static void WriteAzureServiceBusToManifest(AzureServiceBusResource resource, Utf8JsonWriter jsonWriter) + { + jsonWriter.WriteString("type", "azure.servicebus.v1"); + + if (resource.QueueNames.Length > 0) + { + jsonWriter.WriteStartArray("queues"); + foreach (var queueName in resource.QueueNames) + { + jsonWriter.WriteStringValue(queueName); + } + jsonWriter.WriteEndArray(); + } + + if (resource.TopicNames.Length > 0) + { + jsonWriter.WriteStartArray("topics"); + foreach (var topicName in resource.TopicNames) + { + jsonWriter.WriteStringValue(topicName); + } + jsonWriter.WriteEndArray(); + } + } + + /// + /// Adds an Azure Storage resource to the application model. This resource can be used to create Azure blob, table, and queue resources. + /// + /// The . + /// The name of the resource. + /// A reference to the . + public static IResourceBuilder AddAzureStorage(this IDistributedApplicationBuilder builder, string name) + { + var resource = new AzureStorageResource(name); + return builder.AddResource(resource) + .WithAnnotation(new ManifestPublishingCallbackAnnotation(WriteAzureStorageToManifest)); + } + + private static void WriteAzureStorageToManifest(Utf8JsonWriter jsonWriter) + { + jsonWriter.WriteString("type", "azure.storage.v1"); + } + + /// + /// Adds an Azure blob storage resource to the application model. This resource requires an to be added to the application model. + /// + /// The Azure storage resource builder. + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// A reference to the . + public static IResourceBuilder AddBlobs(this IResourceBuilder storageBuilder, string name) + { + var resource = new AzureBlobStorageResource(name, storageBuilder.Resource); + return storageBuilder.ApplicationBuilder.AddResource(resource) + .WithAnnotation(new ManifestPublishingCallbackAnnotation(json => WriteBlobStorageToManifest(json, resource))); + } + + private static void WriteBlobStorageToManifest(Utf8JsonWriter json, AzureBlobStorageResource resource) + { + json.WriteString("type", "azure.storage.blob.v1"); + json.WriteString("parent", resource.Parent.Name); + } + + /// + /// Adds an Azure table storage resource to the application model. This resource requires an to be added to the application model. + /// + /// The Azure storage resource builder. + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// A reference to the . + public static IResourceBuilder AddTables(this IResourceBuilder storageBuilder, string name) + { + var resource = new AzureTableStorageResource(name, storageBuilder.Resource); + return storageBuilder.ApplicationBuilder.AddResource(resource) + .WithAnnotation(new ManifestPublishingCallbackAnnotation(json => WriteTableStorageToManifest(json, resource))); + } + + private static void WriteTableStorageToManifest(Utf8JsonWriter json, AzureTableStorageResource resource) + { + json.WriteString("type", "azure.storage.table.v1"); + json.WriteString("parent", resource.Parent.Name); + } + + /// + /// Adds an Azure queue storage resource to the application model. This resource requires an to be added to the application model. + /// + /// The Azure storage resource builder. + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// A reference to the . + public static IResourceBuilder AddQueues(this IResourceBuilder builder, string name) + { + var resource = new AzureQueueStorageResource(name, builder.Resource); + return builder.ApplicationBuilder.AddResource(resource) + .WithAnnotation(new ManifestPublishingCallbackAnnotation(json => WriteQueueStorageToManifest(json, resource))); + } + + private static void WriteQueueStorageToManifest(Utf8JsonWriter json, AzureQueueStorageResource resource) + { + json.WriteString("type", "azure.storage.queue.v1"); + json.WriteString("parent", resource.Parent.Name); + } + + /// + /// Configures an Azure Storage resource to be emulated using Azurite. This resource requires an to be added to the application model. + /// + /// The Azure storage resource builder. + /// The port used for the blob endpoint. + /// The port used for the queue endpoint. + /// The port used for the table endpoint. + /// A reference to the . + public static IResourceBuilder UseEmulator(this IResourceBuilder builder, int? blobPort = null, int? queuePort = null, int? tablePort = null) + { + return builder.WithAnnotation(new ServiceBindingAnnotation(ProtocolType.Tcp, name: "blob", port: blobPort, containerPort: 10000)) + .WithAnnotation(new ServiceBindingAnnotation(ProtocolType.Tcp, name: "queue", port: queuePort, containerPort: 10001)) + .WithAnnotation(new ServiceBindingAnnotation(ProtocolType.Tcp, name: "table", port: tablePort, containerPort: 10002)) + .WithAnnotation(new ContainerImageAnnotation { Image = "mcr.microsoft.com/azure-storage/azurite", Tag = "latest" }); + } + + /// + /// Adds an Azure Redis resource to the application model. + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// A reference to the . + public static IResourceBuilder AddAzureRedis(this IDistributedApplicationBuilder builder, string name) + { + var resource = new AzureRedisResource(name); + return builder.AddResource(resource) + .WithAnnotation(new ManifestPublishingCallbackAnnotation(WriteAzureRedisToManifest)); + } + + private static void WriteAzureRedisToManifest(Utf8JsonWriter writer) + { + writer.WriteString("type", "azure.redis.v1"); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure/AzureServiceBusResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure/AzureServiceBusResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure/AzureServiceBusResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure/AzureServiceBusResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public class AzureServiceBusResource(string name) : Resource(name), IAzureResource, IResourceWithConnectionString +{ + // This is the full uri to the service bus namespace e.g namespace.servicebus.windows.net + public string? ServiceBusEndpoint { get; set; } + + public string[] QueueNames { get; set; } = []; + public string[] TopicNames { get; set; } = []; + + public string? GetConnectionString() => ServiceBusEndpoint; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure/AzureStorageResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure/AzureStorageResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure/AzureStorageResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure/AzureStorageResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Text; + +namespace Aspire.Hosting.ApplicationModel; + +public class AzureStorageResource(string name) : Resource(name), IAzureResource +{ + public Uri? TableUri { get; set; } + public Uri? QueueUri { get; set; } + public Uri? BlobUri { get; set; } + + public bool IsEmulator => this.IsContainer(); + + internal string? GetTableConnectionString() => IsEmulator + ? AzureStorageEmulatorConnectionString.Create(tablePort: GetEmulatorPort("table")) + : TableUri?.ToString(); + + internal string? GetQueueConnectionString() => IsEmulator + ? AzureStorageEmulatorConnectionString.Create(queuePort: GetEmulatorPort("queue")) + : QueueUri?.ToString(); + + internal string? GetBlobConnectionString() => IsEmulator + ? AzureStorageEmulatorConnectionString.Create(blobPort: GetEmulatorPort("blob")) + : BlobUri?.ToString(); + + private int GetEmulatorPort(string endpointName) => + Annotations + .OfType() + .FirstOrDefault(x => x.Name == endpointName) + ?.Port + ?? throw new DistributedApplicationException($"Azure storage resource does not have endpoint annotation with name '{endpointName}'."); +} + +public class AzureTableStorageResource(string name, AzureStorageResource storage) : Resource(name), + IAzureResource, + IResourceWithConnectionString, + IResourceWithParent +{ + public AzureStorageResource Parent => storage; + + public string? GetConnectionString() => Parent.GetTableConnectionString(); +} + +public class AzureBlobStorageResource(string name, AzureStorageResource storage) : Resource(name), + IAzureResource, + IResourceWithConnectionString, + IResourceWithParent +{ + public AzureStorageResource Parent => storage; + + public string? GetConnectionString() => Parent.GetBlobConnectionString(); +} + +public class AzureQueueStorageResource(string name, AzureStorageResource storage) : Resource(name), + IAzureResource, + IResourceWithConnectionString, + IResourceWithParent +{ + public AzureStorageResource Parent => storage; + + public string? GetConnectionString() => Parent.GetQueueConnectionString(); +} + +static file class AzureStorageEmulatorConnectionString +{ + // Use defaults from https://learn.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string#connect-to-the-emulator-account-using-the-shortcut + private const string ConnectionStringHeader = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"; + private const string BlobEndpointTemplate = "BlobEndpoint=http://127.0.0.1:{0}/devstoreaccount1;"; + private const string QueueEndpointTemplate = "QueueEndpoint=http://127.0.0.1:{0}/devstoreaccount1;"; + private const string TableEndpointTemplate = "TableEndpoint=http://127.0.0.1:{0}/devstoreaccount1;"; + + public static string Create(int? blobPort = null, int? queuePort = null, int? tablePort = null) + { + var builder = new StringBuilder(ConnectionStringHeader); + + if (blobPort is not null) + { + builder.AppendFormat(CultureInfo.InvariantCulture, BlobEndpointTemplate, blobPort); + } + if (queuePort is not null) + { + builder.AppendFormat(CultureInfo.InvariantCulture, QueueEndpointTemplate, queuePort); + } + if (tablePort is not null) + { + builder.AppendFormat(CultureInfo.InvariantCulture, TableEndpointTemplate, tablePort); + } + + return builder.ToString(); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure/IAzureResource.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure/IAzureResource.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure/IAzureResource.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure/IAzureResource.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +public interface IAzureResource : IResource +{ + +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Aspire.Hosting.Azure.Provisioning.csproj dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Aspire.Hosting.Azure.Provisioning.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Aspire.Hosting.Azure.Provisioning.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Aspire.Hosting.Azure.Provisioning.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,21 @@ + + + + $(NetCurrent) + true + + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/AzureProvisinerOptions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/AzureProvisinerOptions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/AzureProvisinerOptions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/AzureProvisinerOptions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.Azure.Provisioning; + +internal sealed class AzureProvisinerOptions +{ + public string? SubscriptionId { get; set; } + + public string? ResourceGroup { get; set; } + + public bool? AllowResourceGroupCreation { get; set; } + + public string? Location { get; set; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/AzureProvisionerExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/AzureProvisionerExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/AzureProvisionerExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/AzureProvisionerExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Azure; +using Aspire.Hosting.Azure.Provisioning; +using Aspire.Hosting.Lifecycle; +using Azure.ResourceManager; +using Azure.ResourceManager.KeyVault; +using Azure.ResourceManager.Redis; +using Azure.ResourceManager.Resources; +using Azure.ResourceManager.ServiceBus; +using Azure.ResourceManager.Storage; +using Microsoft.Extensions.DependencyInjection; + +namespace Aspire.Hosting; + +public static class AzureProvisionerExtensions +{ + /// + /// Adds support for generating azure resources dynamically during application startup. + /// The application must configure the appropriate subscription, location. + /// + public static IDistributedApplicationBuilder AddAzureProvisioning(this IDistributedApplicationBuilder builder) + { + builder.Services.AddLifecycleHook(); + + // Attempt to read azure configuration from configuration + builder.Services.AddOptions() + .BindConfiguration("Azure"); + + // We're adding 2 because there's no easy way to enumerate all keys and all service types + builder.AddAzureProvisioner(); + builder.AddResourceEnumerator(resourceGroup => resourceGroup.GetKeyVaults(), resource => resource.Data.Tags); + + builder.AddAzureProvisioner(); + builder.AddResourceEnumerator(resourceGroup => resourceGroup.GetStorageAccounts(), resource => resource.Data.Tags); + + builder.AddAzureProvisioner(); + builder.AddResourceEnumerator(resourceGroup => resourceGroup.GetServiceBusNamespaces(), resource => resource.Data.Tags); + + builder.AddAzureProvisioner(); + builder.AddResourceEnumerator(resourceGroup => resourceGroup.GetAllRedis(), resource => resource.Data.Tags); + return builder; + } + + internal static IDistributedApplicationBuilder AddAzureProvisioner(this IDistributedApplicationBuilder builder) + where TResource : IAzureResource + where TProvisioner : AzureResourceProvisioner + { + // This lets us avoid using open generics in the caller, we can use keyed lookup instead + builder.Services.AddKeyedSingleton(typeof(TResource)); + return builder; + } + + internal static IDistributedApplicationBuilder AddResourceEnumerator(this IDistributedApplicationBuilder builder, + Func> getResources, + Func> getTags) + where TResource : ArmResource + { + builder.Services.AddSingleton(new AzureResourceEnumerator(getResources, getTags)); + return builder; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/IAzureResourceEnumerator.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/IAzureResourceEnumerator.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/IAzureResourceEnumerator.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/IAzureResourceEnumerator.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Azure.ResourceManager; +using Azure.ResourceManager.Resources; + +namespace Aspire.Hosting.Azure.Provisioning; + +internal interface IAzureResourceEnumerator +{ + IAsyncEnumerable GetResources(ResourceGroupResource resourceGroup); + IDictionary GetTags(ArmResource resource); +} + +internal sealed class AzureResourceEnumerator( + Func> getResources, + Func> getTags) : IAzureResourceEnumerator + where TResource : ArmResource +{ + public IAsyncEnumerable GetResources(ResourceGroupResource resourceGroup) => getResources(resourceGroup); + + public IDictionary GetTags(TResource resource) => getTags(resource); + + async IAsyncEnumerable IAzureResourceEnumerator.GetResources(ResourceGroupResource resourceGroup) + { + await foreach (var resource in GetResources(resourceGroup)) + { + yield return resource; + } + } + + IDictionary IAzureResourceEnumerator.GetTags(ArmResource resource) + { + return GetTags((TResource)resource); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/JsonExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/JsonExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/JsonExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/JsonExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Json.Nodes; + +namespace Aspire.Hosting.Azure; + +internal static class JsonExtensions +{ + public static JsonNode Prop(this JsonNode obj, string key) + { + var node = obj[key]; + if (node is not null) + { + return node; + } + + node = new JsonObject(); + obj.AsObject().Add(key, node); + return node; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/AzureProvisioner.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/AzureProvisioner.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/AzureProvisioner.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/AzureProvisioner.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,337 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Nodes; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Azure.Provisioning; +using Aspire.Hosting.Lifecycle; +using Aspire.Hosting.Publishing; +using Azure; +using Azure.Core; +using Azure.Identity; +using Azure.ResourceManager; +using Azure.ResourceManager.Resources; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.UserSecrets; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Aspire.Hosting.Azure; + +// Provisions azure resources for development purposes +internal sealed class AzureProvisioner( + IOptions options, + IOptions publishingOptions, + IConfiguration configuration, + IHostEnvironment environment, + ILogger logger, + IServiceProvider serviceProvider, + IEnumerable resourceEnumerators) : IDistributedApplicationLifecycleHook +{ + internal const string AspireResourceNameTag = "aspire-resource-name"; + + private readonly AzureProvisinerOptions _options = options.Value; + + public async Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default) + { + // TODO: Make this more general purpose + if (publishingOptions.Value.Publisher == "manifest") + { + return; + } + + var azureResources = appModel.Resources.OfType(); + if (!azureResources.OfType().Any()) + { + return; + } + + try + { + await ProvisionAzureResources(configuration, environment, logger, azureResources, cancellationToken).ConfigureAwait(false); + } + catch (MissingConfigurationException ex) + { + logger.LogWarning(ex, "Required configuration is missing."); + } + catch (Exception ex) + { + logger.LogError(ex, "Error provisioning Azure resources."); + } + } + + private async Task ProvisionAzureResources(IConfiguration configuration, IHostEnvironment environment, ILogger logger, IEnumerable azureResources, CancellationToken cancellationToken) + { + var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions() + { + ExcludeManagedIdentityCredential = true, + ExcludeWorkloadIdentityCredential = true, + ExcludeAzurePowerShellCredential = true, + CredentialProcessTimeout = TimeSpan.FromSeconds(15) + }); + + var subscriptionId = _options.SubscriptionId ?? throw new MissingConfigurationException("An azure subscription id is required. Set the Azure:SubscriptionId configuration value."); + var location = _options.Location switch + { + null => throw new MissingConfigurationException("An azure location/region is required. Set the Azure:Location configuration value."), + string loc => new AzureLocation(loc) + }; + + var armClient = new ArmClient(credential, subscriptionId); + + var subscriptionLazy = new Lazy>(async () => + { + logger.LogInformation("Getting default subscription..."); + + var value = await armClient.GetDefaultSubscriptionAsync(cancellationToken).ConfigureAwait(false); + + logger.LogInformation("Default subscription: {name} ({subscriptionId})", value.Data.DisplayName, value.Id); + + return value; + }); + + Lazy> resourceGroupAndLocationLazy = new(async () => + { + // Name of the resource group to create based on the machine name and application name + var (resourceGroupName, createIfAbsent) = _options.ResourceGroup switch + { + null => ($"{Environment.MachineName.ToLowerInvariant()}-{environment.ApplicationName.ToLowerInvariant()}-rg", true), + string rg => (rg, _options.AllowResourceGroupCreation ?? false) + }; + + var subscription = await subscriptionLazy.Value.ConfigureAwait(false); + + var resourceGroups = subscription.GetResourceGroups(); + ResourceGroupResource? resourceGroup = null; + AzureLocation location = new(_options.Location); + try + { + var response = await resourceGroups.GetAsync(resourceGroupName, cancellationToken).ConfigureAwait(false); + resourceGroup = response.Value; + location = resourceGroup.Data.Location; + + logger.LogInformation("Using existing resource group {rgName}.", resourceGroup.Data.Name); + } + catch (Exception) + { + if (!createIfAbsent) + { + throw; + } + + // REVIEW: Is it possible to do this without an exception? + + logger.LogInformation("Creating resource group {rgName} in {location}...", resourceGroupName, location); + + var rgData = new ResourceGroupData(location); + rgData.Tags.Add("aspire", "true"); + var operation = await resourceGroups.CreateOrUpdateAsync(WaitUntil.Completed, resourceGroupName, rgData, cancellationToken).ConfigureAwait(false); + resourceGroup = operation.Value; + + logger.LogInformation("Resource group {rgName} created.", resourceGroup.Data.Name); + } + + return (resourceGroup, location); + }); + + var principalIdLazy = new Lazy>(async () => Guid.Parse(await GetUserPrincipalAsync(credential, cancellationToken).ConfigureAwait(false))); + + var resourceMapLazy = new Lazy>>(async () => + { + var resourceMap = new Dictionary(); + + var (resourceGroup, _) = await resourceGroupAndLocationLazy.Value.ConfigureAwait(false); + + // Enumerate all known resources and look for aspire tags + foreach (var enumerator in resourceEnumerators) + { + await PopulateExistingAspireResources( + resourceGroup, + enumerator.GetResources, + enumerator.GetTags, + resourceMap, + cancellationToken).ConfigureAwait(false); + } + + return resourceMap; + }); + + var tasks = new List(); + + // Try to find the user secrets path so that provisioners can persist connection information. + static string? GetUserSecretsPath() + { + return Assembly.GetEntryAssembly()?.GetCustomAttribute()?.UserSecretsId switch + { + null => Environment.GetEnvironmentVariable("DOTNET_USER_SECRETS_ID"), + string id => UserSecretsPathHelper.GetSecretsPathFromSecretsId(id) + }; + } + + var userSecretsPath = GetUserSecretsPath(); + + ResourceGroupResource? resourceGroup = null; + SubscriptionResource? subscription = null; + Dictionary? resourceMap = null; + Guid? principalId = default; + var usedResources = new HashSet(); + + var userSecrets = userSecretsPath is null || !File.Exists(userSecretsPath) ? [] : JsonNode.Parse(File.ReadAllText(userSecretsPath))!.AsObject(); + + foreach (var resource in azureResources) + { + usedResources.Add(resource.Name); + + var provisoner = serviceProvider.GetKeyedService(resource.GetType()); + + if (provisoner is null) + { + logger.LogWarning("No provisioner found for {resourceType} skipping.", resource.GetType().Name); + continue; + } + + if (!provisoner.ShouldProvision(configuration, resource)) + { + logger.LogInformation("Skipping {resourceName} because it is not configured to be provisioned.", resource.Name); + continue; + } + + if (provisoner.ConfigureResource(configuration, resource)) + { + logger.LogInformation("Using connection information stored in user secrets for {resourceName}.", resource.Name); + + continue; + } + + subscription ??= await subscriptionLazy.Value.ConfigureAwait(false); + + if (resourceGroup is null) + { + (resourceGroup, location) = await resourceGroupAndLocationLazy.Value.ConfigureAwait(false); + } + + resourceMap ??= await resourceMapLazy.Value.ConfigureAwait(false); + principalId ??= await principalIdLazy.Value.ConfigureAwait(false); + + var task = provisoner.GetOrCreateResourceAsync(armClient, + subscription, + resourceGroup, + resourceMap, + location, + resource, + principalId.Value, + userSecrets, + cancellationToken); + + tasks.Add(task); + } + + if (tasks.Count > 0) + { + await Task.WhenAll(tasks).ConfigureAwait(false); + + // If we created any resources then save the user secrets + if (userSecretsPath is not null) + { + // Ensure directory exists before attempting to create secrets file + Directory.CreateDirectory(Path.GetDirectoryName(userSecretsPath)!); + File.WriteAllText(userSecretsPath, userSecrets.ToString()); + + logger.LogInformation("Azure resource connection strings saved to user secrets."); + } + } + + // Do this in the background to avoid blocking startup + _ = Task.Run(async () => + { + logger.LogInformation("Cleaning up unused resources..."); + + resourceMap ??= await resourceMapLazy.Value.ConfigureAwait(false); + + // Clean up any left over resources that are no longer in the model + foreach (var (name, sa) in resourceMap) + { + if (usedResources.Contains(name)) + { + continue; + } + + var response = await armClient.GetGenericResources().GetAsync(sa.Id, cancellationToken).ConfigureAwait(false); + + logger.LogInformation("Deleting unused resource {keyVaultName} which maps to resource name {name}.", sa.Id, name); + + await response.Value.DeleteAsync(WaitUntil.Started, cancellationToken).ConfigureAwait(false); + } + }, + cancellationToken); + } + + private static async Task PopulateExistingAspireResources( + ResourceGroupResource resourceGroup, + Func> getCollection, + Func> getTags, + Dictionary map, + CancellationToken cancellationToken) + where TResource : ArmResource + { + await foreach (var r in getCollection(resourceGroup).WithCancellation(cancellationToken)) + { + var tags = getTags(r); + if (tags.TryGetValue(AspireResourceNameTag, out var aspireResourceName)) + { + map[aspireResourceName] = r; + } + } + } + + internal async Task GetUserPrincipalAsync(TokenCredential credential, CancellationToken cancellationToken) + { + var response = await credential.GetTokenAsync(new(["https://graph.windows.net/.default"]), cancellationToken).ConfigureAwait(false); + + static string ParseToken(in AccessToken response) + { + // Parse the access token to get the user's object id (this is their principal id) + + var parts = response.Token.Split('.'); + var part = parts[1]; + var convertedToken = part.ToString().Replace('_', '/').Replace('-', '+'); + + switch (part.Length % 4) + { + case 2: + convertedToken += "=="; + break; + case 3: + convertedToken += "="; + break; + } + var bytes = Convert.FromBase64String(convertedToken); + Utf8JsonReader reader = new(bytes); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.PropertyName) + { + var header = reader.GetString(); + if (header == "oid") + { + reader.Read(); + return reader.GetString()!; + } + reader.Read(); + } + } + return string.Empty; + } + + return ParseToken(response); + } + + sealed class MissingConfigurationException(string message) : Exception(message) + { + + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/AzureRedisProvisioner.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/AzureRedisProvisioner.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/AzureRedisProvisioner.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/AzureRedisProvisioner.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,83 @@ +// 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; +using System.Text.Json.Nodes; +using Aspire.Hosting.ApplicationModel; +using Azure; +using Azure.Core; +using Azure.ResourceManager; +using Azure.ResourceManager.Redis; +using Azure.ResourceManager.Redis.Models; +using Azure.ResourceManager.Resources; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +using RedisArmResource = Azure.ResourceManager.Redis.RedisResource; + +namespace Aspire.Hosting.Azure.Provisioning; + +internal sealed class AzureRedisProvisioner(ILogger logger) : AzureResourceProvisioner +{ + public override bool ConfigureResource(IConfiguration configuration, AzureRedisResource resource) + { + if (configuration.GetConnectionString(resource.Name) is string connectionString) + { + resource.ConnectionString = connectionString; + return true; + } + + return false; + } + + public override async Task GetOrCreateResourceAsync( + ArmClient armClient, + SubscriptionResource subscription, + ResourceGroupResource resourceGroup, + Dictionary resourceMap, + AzureLocation location, + AzureRedisResource resource, + Guid principalId, + JsonObject userSecrets, + CancellationToken cancellationToken) + { + + resourceMap.TryGetValue(resource.Name, out var azureResource); + + if (azureResource is not null && azureResource is not RedisArmResource) + { + logger.LogWarning("Resource {resourceName} is not a redis resource. Deleting it.", resource.Name); + + await armClient.GetGenericResource(azureResource.Id).DeleteAsync(WaitUntil.Started, cancellationToken).ConfigureAwait(false); + } + + var redisResource = azureResource as RedisArmResource; + + if (redisResource is null) + { + var redisName = Guid.NewGuid().ToString().Replace("-", string.Empty)[0..20]; + + logger.LogInformation("Creating redis {redisName} in {location}...", redisName, location); + + var redisCreateOrUpdateContent = new RedisCreateOrUpdateContent(location, new RedisSku(RedisSkuName.Basic, RedisSkuFamily.BasicOrStandard, 0)); + redisCreateOrUpdateContent.Tags.Add(AzureProvisioner.AspireResourceNameTag, resource.Name); + + var sw = Stopwatch.StartNew(); + var operation = await resourceGroup.GetAllRedis().CreateOrUpdateAsync(WaitUntil.Completed, redisName, redisCreateOrUpdateContent, cancellationToken).ConfigureAwait(false); + redisResource = operation.Value; + sw.Stop(); + + logger.LogInformation("Redis {redisName} created in {elapsed}", redisResource.Data.Name, sw.Elapsed); + } + + // This must be an explicit call to get the keys + var keysOperation = await redisResource.GetKeysAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + var keys = keysOperation.Value; + + // REVIEW: Do we need to use the port? + resource.ConnectionString = $"{redisResource.Data.HostName},ssl=true,password={keys.PrimaryKey}"; + + var connectionStrings = userSecrets.Prop("ConnectionStrings"); + connectionStrings[resource.Name] = resource.ConnectionString; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/AzureResourceProvisionerOfT.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/AzureResourceProvisionerOfT.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/AzureResourceProvisionerOfT.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/AzureResourceProvisionerOfT.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Azure.Core; +using Azure.ResourceManager.Resources; +using Azure.ResourceManager; +using System.Text.Json.Nodes; +using Microsoft.Extensions.Configuration; +using Azure.ResourceManager.Authorization.Models; +using Azure.ResourceManager.Authorization; +using Azure; +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting.Azure.Provisioning; + +internal interface IAzureResourceProvisioner +{ + bool ConfigureResource(IConfiguration configuration, IAzureResource resource); + + bool ShouldProvision(IConfiguration configuration, IAzureResource resource); + + Task GetOrCreateResourceAsync( + ArmClient armClient, + SubscriptionResource subscription, + ResourceGroupResource resourceGroup, + Dictionary resourceMap, + AzureLocation location, + IAzureResource resource, + Guid principalId, + JsonObject userSecrets, + CancellationToken cancellationToken); +} + +internal abstract class AzureResourceProvisioner : IAzureResourceProvisioner + where TResource : IAzureResource +{ + bool IAzureResourceProvisioner.ConfigureResource(IConfiguration configuration, IAzureResource resource) => + ConfigureResource(configuration, (TResource)resource); + + bool IAzureResourceProvisioner.ShouldProvision(IConfiguration configuration, IAzureResource resource) => + ShouldProvision(configuration, (TResource)resource); + + Task IAzureResourceProvisioner.GetOrCreateResourceAsync( + ArmClient armClient, + SubscriptionResource subscription, + ResourceGroupResource resourceGroup, + Dictionary resourceMap, + AzureLocation location, + IAzureResource resource, + Guid principalId, + JsonObject userSecrets, + CancellationToken cancellationToken) + => GetOrCreateResourceAsync(armClient, subscription, resourceGroup, resourceMap, location, (TResource)resource, principalId, userSecrets, cancellationToken); + + public abstract bool ConfigureResource(IConfiguration configuration, TResource resource); + + public virtual bool ShouldProvision(IConfiguration configuration, TResource resource) => true; + + public abstract Task GetOrCreateResourceAsync( + ArmClient armClient, + SubscriptionResource subscription, + ResourceGroupResource resourceGroup, + Dictionary resourceMap, + AzureLocation location, + TResource resource, + Guid principalId, + JsonObject userSecrets, + CancellationToken cancellationToken); + + protected static ResourceIdentifier CreateRoleDefinitionId(SubscriptionResource subscription, string roleDefinitionId) => + new($"{subscription.Id}/providers/Microsoft.Authorization/roleDefinitions/{roleDefinitionId}"); + + protected static async Task DoRoleAssignmentAsync( + ArmClient armClient, + ResourceIdentifier resourceId, + Guid principalId, + ResourceIdentifier roleDefinitionId, + CancellationToken cancellationToken) + { + var roleAssignments = armClient.GetRoleAssignments(resourceId); + await foreach (var ra in roleAssignments.GetAllAsync(cancellationToken: cancellationToken)) + { + if (ra.Data.PrincipalId == principalId && + ra.Data.RoleDefinitionId.Equals(roleDefinitionId)) + { + return; + } + } + + var roleAssignmentInfo = new RoleAssignmentCreateOrUpdateContent(roleDefinitionId, principalId); + + var roleAssignmentId = Guid.NewGuid().ToString(); + await roleAssignments.CreateOrUpdateAsync(WaitUntil.Completed, roleAssignmentId, roleAssignmentInfo, cancellationToken).ConfigureAwait(false); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/KeyVaultProvisoner.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/KeyVaultProvisoner.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/KeyVaultProvisoner.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/KeyVaultProvisoner.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Nodes; +using Aspire.Hosting.ApplicationModel; +using Azure; +using Azure.Core; +using Azure.ResourceManager; +using Azure.ResourceManager.KeyVault; +using Azure.ResourceManager.KeyVault.Models; +using Azure.ResourceManager.Resources; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Aspire.Hosting.Azure.Provisioning; + +internal sealed class KeyVaultProvisoner(ILogger logger) : AzureResourceProvisioner +{ + public override bool ConfigureResource(IConfiguration configuration, AzureKeyVaultResource resource) + { + if (configuration.GetConnectionString(resource.Name) is string vaultUrl) + { + resource.VaultUri = new(vaultUrl); + return true; + } + + return false; + } + + public override async Task GetOrCreateResourceAsync( + ArmClient armClient, + SubscriptionResource subscription, + ResourceGroupResource resourceGroup, + Dictionary resourceMap, + AzureLocation location, + AzureKeyVaultResource keyVault, + Guid principalId, + JsonObject userSecrets, + CancellationToken cancellationToken) + { + resourceMap.TryGetValue(keyVault.Name, out var azureResource); + + if (azureResource is not null && azureResource is not KeyVaultResource) + { + logger.LogWarning("Resource {resourceName} is not a key vault resource. Deleting it.", keyVault.Name); + + await armClient.GetGenericResource(azureResource.Id).DeleteAsync(WaitUntil.Started, cancellationToken).ConfigureAwait(false); + } + + var keyVaultResource = azureResource as KeyVaultResource; + + if (keyVaultResource is null) + { + // A vault's name must be between 3-24 alphanumeric characters. The name must begin with a letter, end with a letter or digit, and not contain consecutive hyphens. + // Follow this link for more information: https://go.microsoft.com/fwlink/?linkid=2147742 + var vaultName = $"v{Guid.NewGuid().ToString().Replace("-", string.Empty)[0..20]}"; + + logger.LogInformation("Creating key vault {vaultName} in {location}...", vaultName, location); + + var properties = new KeyVaultProperties(subscription.Data.TenantId!.Value, new KeyVaultSku(KeyVaultSkuFamily.A, KeyVaultSkuName.Standard)) + { + EnabledForTemplateDeployment = true, + EnableRbacAuthorization = true + }; + var parameters = new KeyVaultCreateOrUpdateContent(location, properties); + parameters.Tags.Add(AzureProvisioner.AspireResourceNameTag, keyVault.Name); + + var operation = await resourceGroup.GetKeyVaults().CreateOrUpdateAsync(WaitUntil.Completed, vaultName, parameters, cancellationToken).ConfigureAwait(false); + keyVaultResource = operation.Value; + + logger.LogInformation("Key vault {vaultName} created.", keyVaultResource.Data.Name); + } + keyVault.VaultUri = keyVaultResource.Data.Properties.VaultUri; + + var connectionStrings = userSecrets.Prop("ConnectionStrings"); + connectionStrings[keyVault.Name] = keyVault.VaultUri.ToString(); + + // Key Vault Administrator + // https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#key-vault-administrator + var roleDefinitionId = CreateRoleDefinitionId(subscription, "00482a5a-887f-4fb3-b363-3b7fe8e74483"); + + await DoRoleAssignmentAsync(armClient, keyVaultResource.Id, principalId, roleDefinitionId, cancellationToken).ConfigureAwait(false); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/ServiceBusProvisioner.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/ServiceBusProvisioner.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/ServiceBusProvisioner.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/ServiceBusProvisioner.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,158 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Nodes; +using Aspire.Hosting.ApplicationModel; +using Azure; +using Azure.Core; +using Azure.ResourceManager; +using Azure.ResourceManager.Resources; +using Azure.ResourceManager.ServiceBus; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Aspire.Hosting.Azure.Provisioning; + +internal sealed class ServiceBusProvisioner(ILogger logger) : AzureResourceProvisioner +{ + public override bool ConfigureResource(IConfiguration configuration, AzureServiceBusResource resource) + { + if (configuration.GetConnectionString(resource.Name) is string endpoint) + { + resource.ServiceBusEndpoint = endpoint; + return true; + } + + return false; + } + + public override async Task GetOrCreateResourceAsync( + ArmClient armClient, + SubscriptionResource subscription, + ResourceGroupResource resourceGroup, + Dictionary resourceMap, + AzureLocation location, + AzureServiceBusResource resource, + Guid principalId, + JsonObject userSecrets, + CancellationToken cancellationToken) + { + resourceMap.TryGetValue(resource.Name, out var azureResource); + + if (azureResource is not null && azureResource is not ServiceBusNamespaceResource) + { + logger.LogWarning("Resource {resourceName} is not a service bus namespace. Deleting it.", resource.Name); + + await armClient.GetGenericResource(azureResource.Id).DeleteAsync(WaitUntil.Started, cancellationToken).ConfigureAwait(false); + } + + var serviceBusNamespace = azureResource as ServiceBusNamespaceResource; + + if (serviceBusNamespace is null) + { + logger.LogInformation("Creating service bus namespace in {location}...", location); + + var attempts = 0; + + while (true) + { + try + { + // ^[a-zA-Z][a-zA-Z0-9-]*$ + var namespaceName = Guid.NewGuid().ToString(); + + var parameters = new ServiceBusNamespaceData(location); + parameters.Tags.Add(AzureProvisioner.AspireResourceNameTag, resource.Name); + + // Now we can create a storage account with defined account name and parameters + var operation = await resourceGroup.GetServiceBusNamespaces().CreateOrUpdateAsync(WaitUntil.Completed, namespaceName, parameters, cancellationToken).ConfigureAwait(false); + serviceBusNamespace = operation.Value; + + // Success + break; + } + catch (RequestFailedException) + { + // We've seen errors like + // "The specified service namespace is invalid" when we know that guids are valid + // service bus namespace names. + if (attempts++ > 3) + { + throw; + } + + await Task.Delay(500, cancellationToken).ConfigureAwait(false); + } + } + + logger.LogInformation("Service bus namespace {namespace} created.", serviceBusNamespace.Data.Name); + } + + // This is the full uri to the service bus namespace e.g https://namespace.servicebus.windows.net:443/ + // the connection strings for the app need the host name only + resource.ServiceBusEndpoint = new Uri(serviceBusNamespace.Data.ServiceBusEndpoint).Host; + + var connectionStrings = userSecrets.Prop("ConnectionStrings"); + connectionStrings[resource.Name] = resource.ServiceBusEndpoint; + + // Now create the queues + var queues = serviceBusNamespace.GetServiceBusQueues(); + var topics = serviceBusNamespace.GetServiceBusTopics(); + + var queuesToCreate = new HashSet(resource.QueueNames); + var topicsToCreate = new HashSet(resource.TopicNames); + + // Delete unused queues + await foreach (var sbQueue in queues.GetAllAsync(cancellationToken: cancellationToken)) + { + if (!resource.QueueNames.Contains(sbQueue.Data.Name)) + { + logger.LogInformation("Deleting queue {queueName}", sbQueue.Data.Name); + + await sbQueue.DeleteAsync(WaitUntil.Started, cancellationToken).ConfigureAwait(false); + } + + // Don't need to create this queue + queuesToCreate.Remove(sbQueue.Data.Name); + } + + await foreach (var sbTopic in topics.GetAllAsync(cancellationToken: cancellationToken)) + { + if (!resource.TopicNames.Contains(sbTopic.Data.Name)) + { + logger.LogInformation("Deleting topic {topicName}", sbTopic.Data.Name); + + await sbTopic.DeleteAsync(WaitUntil.Started, cancellationToken).ConfigureAwait(false); + } + + // Don't need to create this topic + topicsToCreate.Remove(sbTopic.Data.Name); + } + + // Create the remaining queues + foreach (var queueName in queuesToCreate) + { + logger.LogInformation("Creating queue {queueName}...", queueName); + + await queues.CreateOrUpdateAsync(WaitUntil.Completed, queueName, new ServiceBusQueueData(), cancellationToken).ConfigureAwait(false); + + logger.LogInformation("Queue {queueName} created.", queueName); + } + + // Create the remaining topics + foreach (var topicName in topicsToCreate) + { + logger.LogInformation("Creating topic {topicName}...", topicName); + + await topics.CreateOrUpdateAsync(WaitUntil.Completed, topicName, new ServiceBusTopicData(), cancellationToken).ConfigureAwait(false); + + logger.LogInformation("Topic {topicName} created.", topicName); + } + + // Azure Service Bus Data Owner + // https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#azure-service-bus-data-owner + var roleDefinitionId = CreateRoleDefinitionId(subscription, "090c5cfd-751d-490a-894a-3ce6f1109419"); + + await DoRoleAssignmentAsync(armClient, serviceBusNamespace.Id, principalId, roleDefinitionId, cancellationToken).ConfigureAwait(false); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/StorageProvisioner.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/StorageProvisioner.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/StorageProvisioner.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/StorageProvisioner.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,111 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Nodes; +using Azure; +using Azure.Core; +using Azure.ResourceManager; +using Azure.ResourceManager.Resources; +using Azure.ResourceManager.Storage.Models; +using Azure.ResourceManager.Storage; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting.Azure.Provisioning; + +internal sealed class StorageProvisioner(ILogger logger) : AzureResourceProvisioner +{ + public override bool ConfigureResource(IConfiguration configuration, AzureStorageResource resource) + { + // Storage isn't a connection string because it has multiple endpoints + var storageSection = configuration.GetSection($"Azure:Storage:{resource.Name}"); + var tableUrl = storageSection["TableUri"]; + var blobUrl = storageSection["BlobUri"]; + var queueUrl = storageSection["QueueUri"]; + + // If any of these is null then we need to create/get the storage account + if (tableUrl is not null && blobUrl is not null && queueUrl is not null) + { + resource.TableUri = new Uri(tableUrl); + resource.BlobUri = new Uri(blobUrl); + resource.QueueUri = new Uri(queueUrl); + + return true; + } + return false; + } + + public override bool ShouldProvision(IConfiguration configuration, AzureStorageResource resource) => + !resource.IsEmulator; + + public override async Task GetOrCreateResourceAsync( + ArmClient armClient, + SubscriptionResource subscription, + ResourceGroupResource resourceGroup, + Dictionary resourceMap, + AzureLocation location, + AzureStorageResource resource, + Guid principalId, + JsonObject userSecrets, + CancellationToken cancellationToken) + { + resourceMap.TryGetValue(resource.Name, out var azureResource); + + if (azureResource is not null && azureResource is not StorageAccountResource) + { + logger.LogWarning("Resource {resourceName} is not a storage account. Deleting it.", resource.Name); + + await armClient.GetGenericResource(azureResource.Id).DeleteAsync(WaitUntil.Started, cancellationToken).ConfigureAwait(false); + } + + var storageAccount = azureResource as StorageAccountResource; + + if (storageAccount is null) + { + // Storage account name must be between 3 and 24 characters in length and use numbers and lower-case letters only. + var accountName = Guid.NewGuid().ToString().Replace("-", string.Empty)[0..20]; + + logger.LogInformation("Creating storage account {accountName} in {location}...", accountName, location); + + // First we need to define the StorageAccountCreateParameters + var sku = new StorageSku(StorageSkuName.StandardGrs); + var kind = StorageKind.Storage; + var parameters = new StorageAccountCreateOrUpdateContent(sku, kind, location); + parameters.Tags.Add(AzureProvisioner.AspireResourceNameTag, resource.Name); + + // Now we can create a storage account with defined account name and parameters + var accountCreateOperation = await resourceGroup.GetStorageAccounts().CreateOrUpdateAsync(WaitUntil.Completed, accountName, parameters, cancellationToken).ConfigureAwait(false); + storageAccount = accountCreateOperation.Value; + + logger.LogInformation("Storage account {accountName} created.", storageAccount.Data.Name); + } + + resource.BlobUri = storageAccount.Data.PrimaryEndpoints.BlobUri; + resource.TableUri = storageAccount.Data.PrimaryEndpoints.TableUri; + resource.QueueUri = storageAccount.Data.PrimaryEndpoints.QueueUri; + + var resourceEntry = userSecrets.Prop("Azure").Prop("Storage").Prop(resource.Name); + resourceEntry["BlobUri"] = resource.BlobUri.ToString(); + resourceEntry["TableUri"] = resource.TableUri.ToString(); + resourceEntry["QueueUri"] = resource.QueueUri.ToString(); + + // Storage Queue Data Contributor + // https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#storage-queue-data-contributor + var storageQueueDataContributorId = CreateRoleDefinitionId(subscription, "974c5e8b-45b9-4653-ba55-5f855dd0fb88"); + + // Storage Table Data Contributor + // https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#storage-table-data-contributor + var storageDataContributorId = CreateRoleDefinitionId(subscription, "0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3"); + + // Storage Blob Data Contributor + // https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#storage-blob-data-contributor + var storageBlobDataContributorId = CreateRoleDefinitionId(subscription, "81a9662b-bebf-436f-a333-f67b29880f12"); + + var t0 = DoRoleAssignmentAsync(armClient, storageAccount.Id, principalId, storageQueueDataContributorId, cancellationToken); + var t1 = DoRoleAssignmentAsync(armClient, storageAccount.Id, principalId, storageDataContributorId, cancellationToken); + var t2 = DoRoleAssignmentAsync(armClient, storageAccount.Id, principalId, storageBlobDataContributorId, cancellationToken); + + await Task.WhenAll(t0, t1, t2).ConfigureAwait(false); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/UserSecretsPathHelper.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/UserSecretsPathHelper.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Azure.Provisioning/UserSecretsPathHelper.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Azure.Provisioning/UserSecretsPathHelper.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.Azure; + +// Copied from https://github.com/dotnet/runtime/blob/213833ea99b79a4b494b2935e1ccb10b93cd4cbc/src/libraries/Microsoft.Extensions.Configuration.UserSecrets/src/PathHelper.cs + +public class UserSecretsPathHelper +{ + internal const string SecretsFileName = "secrets.json"; + + /// + /// + /// Returns the path to the JSON file that stores user secrets. + /// + /// + /// This uses the current user profile to locate the secrets file on disk in a location outside of source control. + /// + /// + /// The user secret ID. + /// The full path to the secret file. + public static string GetSecretsPathFromSecretsId(string userSecretsId) + { + return InternalGetSecretsPathFromSecretsId(userSecretsId, throwIfNoRoot: true); + } + + /// + /// + /// Returns the path to the JSON file that stores user secrets or throws exception if not found. + /// + /// + /// This uses the current user profile to locate the secrets file on disk in a location outside of source control. + /// + /// + /// The user secret ID. + /// specifies if an exception should be thrown when no root for user secrets is found + /// The full path to the secret file. + internal static string InternalGetSecretsPathFromSecretsId(string userSecretsId, bool throwIfNoRoot) + { + const string userSecretsFallbackDir = "DOTNET_USER_SECRETS_FALLBACK_DIR"; + + // For backwards compat, this checks env vars first before using Env.GetFolderPath + string? appData = Environment.GetEnvironmentVariable("APPDATA"); + string? root = appData // On Windows it goes to %APPDATA%\Microsoft\UserSecrets\ + ?? Environment.GetEnvironmentVariable("HOME") // On Mac/Linux it goes to ~/.microsoft/usersecrets/ + ?? Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + ?? Environment.GetEnvironmentVariable(userSecretsFallbackDir); // this fallback is an escape hatch if everything else fails + + if (string.IsNullOrEmpty(root)) + { + if (throwIfNoRoot) + { + throw new InvalidOperationException($"Missing user secrets location {userSecretsFallbackDir}"); + } + + return string.Empty; + } + + return !string.IsNullOrEmpty(appData) + ? Path.Combine(root, "Microsoft", "UserSecrets", userSecretsId, SecretsFileName) + : Path.Combine(root, ".microsoft", "usersecrets", userSecretsId, SecretsFileName); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/Aspire.Hosting.Dapr.csproj dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/Aspire.Hosting.Dapr.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/Aspire.Hosting.Dapr.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/Aspire.Hosting.Dapr.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,12 @@ + + + + $(NetCurrent) + true + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/CommandLineBuilder.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/CommandLineBuilder.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/CommandLineBuilder.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/CommandLineBuilder.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,164 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Text; + +namespace Aspire.Hosting.Dapr; + +internal delegate IEnumerable CommandLineArgBuilder(); + +internal sealed record CommandLine(string FileName, IEnumerable Arguments) +{ + public string ArgumentString + { + get + { + StringBuilder builder = new(); + + var args = this.Arguments.ToList(); + + for (int i = 0; i < args.Count; i++) + { + var arg = args[i]; + + if (arg is not null) + { + if (i > 0) + { + builder.Append(' '); + } + + builder.Append(arg); + } + } + + return builder.ToString(); + } + } +} + +internal static class CommandLineBuilder +{ + public static CommandLine Create(string fileName, params CommandLineArgBuilder[] argBuilders) + { + return new CommandLine(fileName, argBuilders.SelectMany(builder => builder())); + } +} + +internal static class CommandLineArgs +{ + public static CommandLineArgBuilder Args(params string[] args) + { + return Args((IEnumerable)args); + } + + public static CommandLineArgBuilder Args(IEnumerable? args) + { + return () => (args ?? Enumerable.Empty()); + } + + public static CommandLineArgBuilder Command(params string[] commands) + { + return () => commands; + } + + public static CommandLineArgBuilder Command(CommandLine commandLine) + { + return Command(new[] { commandLine.FileName, commandLine.ArgumentString }); + } + + public static CommandLineArgBuilder Flag(string name) + { + return Flag(name, true); + } + + public static CommandLineArgBuilder Flag(string name, bool? value) + { + return () => value == true ? new[] { name } : Enumerable.Empty(); + } + + public static CommandLineArgBuilder NamedArg(string name, T value, bool assignValue = false) where T : struct + { + return () => + { + string? stringValue = Convert.ToString(value, CultureInfo.InvariantCulture); + + return stringValue is not null + ? NamedStringArg(name, stringValue, assignValue)() + : Enumerable.Empty(); + }; + } + + public static CommandLineArgBuilder NamedArg(string name, T? value, bool assignValue = false) where T : struct + { + return () => + value.HasValue + ? NamedArg(name, value.Value, assignValue)() + : Enumerable.Empty(); + } + + public static CommandLineArgBuilder NamedArg(string name, string? value, bool assignValue = false) + { + return () => + { + return value is not null + ? NamedStringArg(name, value, assignValue)() + : Enumerable.Empty(); + }; + } + + public static CommandLineArgBuilder NamedArg(string name, IEnumerable? values, bool assignValue = false) + { + return () => + { + return (values ?? Enumerable.Empty()).SelectMany(value => NamedArg(name, value, assignValue)()); + }; + } + + private static readonly char[] s_reservedChars = new[] { ' ', '&', '|', '(', ')', '<', '>', '^' }; + + private static CommandLineArgBuilder NamedStringArg(string name, string value, bool assignValue) + { + bool hasReservedChars = value.Any(c => s_reservedChars.Contains(c)) == true; + + return () => + { + return assignValue + ? new[] { $"\"{name}={value}\"" } + : new[] { name, hasReservedChars ? $"\"{value}\"" : value }; + }; + } + + public static CommandLineArgBuilder PostOptionsArgs(params CommandLineArgBuilder[] args) + { + return PostOptionsArgs(null, args); + } + + public static CommandLineArgBuilder PostOptionsArgs(string? separator, params CommandLineArgBuilder[] args) + { + return PostOptionsArgs(separator, (IEnumerable)args); + } + + public static CommandLineArgBuilder PostOptionsArgs(string? separator, IEnumerable args) + { + IEnumerable GeneratePostOptionsArgs() + { + bool postOptions = false; + + foreach (var arg in args.SelectMany(builder => builder())) + { + if (!postOptions) + { + postOptions = true; + + yield return separator ?? "--"; + } + + yield return arg; + } + } + + return GeneratePostOptionsArgs; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/DaprDistributedApplicationLifecycleHook.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/DaprDistributedApplicationLifecycleHook.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/DaprDistributedApplicationLifecycleHook.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/DaprDistributedApplicationLifecycleHook.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Lifecycle; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using System.Globalization; +using System.Net.Sockets; +using static Aspire.Hosting.Dapr.CommandLineArgs; + +namespace Aspire.Hosting.Dapr; + +internal sealed class DaprDistributedApplicationLifecycleHook : IDistributedApplicationLifecycleHook +{ + private readonly IConfiguration _configuration; + private readonly IHostEnvironment _environment; + private readonly DaprOptions _options; + private readonly DaprPortManager _portManager; + + private static readonly string s_defaultDaprPath = + OperatingSystem.IsWindows() + ? Path.Combine(Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.Windows)) ?? "C:", "dapr", "dapr.exe") + : Path.Combine("/usr", "local", "bin", "dapr"); + + private const int DaprHttpPortStartRange = 50001; + + public DaprDistributedApplicationLifecycleHook(IConfiguration configuration, IHostEnvironment environment, DaprOptions options, DaprPortManager portManager) + { + _configuration = configuration; + _environment = environment; + this._options = options; + this._portManager = portManager; + } + + public Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default) + { + var projectResources = appModel.GetProjectResources().ToArray(); + + foreach (var project in projectResources) + { + if (!project.TryGetLastAnnotation(out var projectMetadata)) + { + continue; + } + + if (!project.TryGetLastAnnotation(out var daprAnnotation)) + { + continue; + } + + var projectName = Path.GetFileNameWithoutExtension(projectMetadata.ProjectPath); + + var sidecarOptions = daprAnnotation.Options; + + string fileName = this._options.DaprPath ?? s_defaultDaprPath; + string workingDirectory = Path.GetDirectoryName(projectMetadata.ProjectPath)!; + + var daprAppPortArg = (int? port) => NamedArg("--app-port", port); + var daprGrpcPortArg = (int? port) => NamedArg("--dapr-grpc-port", port); + var daprHttpPortArg = (int? port) => NamedArg("--dapr-http-port", port); + var daprMetricsPortArg = (int? port) => NamedArg("--metrics-port", port); + var daprProfilePortArg = (int? port) => NamedArg("--profile-port", port); + + var daprCommandLine = + CommandLineBuilder + .Create( + fileName, + Command("run"), + daprAppPortArg(sidecarOptions?.AppPort), + daprGrpcPortArg(sidecarOptions?.DaprGrpcPort), + daprHttpPortArg(sidecarOptions?.DaprHttpPort), + daprMetricsPortArg(sidecarOptions?.MetricsPort), + daprProfilePortArg(sidecarOptions?.ProfilePort), + NamedArg("--app-channel-address", sidecarOptions?.AppChannelAddress), + NamedArg("--app-health-check-path", sidecarOptions?.AppHealthCheckPath), + NamedArg("--app-health-probe-interval", sidecarOptions?.AppHealthProbeInterval), + NamedArg("--app-health-probe-timeout", sidecarOptions?.AppHealthProbeTimeout), + NamedArg("--app-health-threshold", sidecarOptions?.AppHealthThreshold), + NamedArg("--app-id", sidecarOptions?.AppId), + NamedArg("--app-max-concurrency", sidecarOptions?.AppMaxConcurrency), + NamedArg("--app-protocol", sidecarOptions?.AppProtocol), + NamedArg("--config", sidecarOptions?.Config), + NamedArg("--dapr-http-max-request-size", sidecarOptions?.DaprHttpMaxRequestSize), + NamedArg("--dapr-http-read-buffer-size", sidecarOptions?.DaprHttpReadBufferSize), + NamedArg("--dapr-internal-grpc-port", sidecarOptions?.DaprInternalGrpcPort), + NamedArg("--dapr-listen-addresses", sidecarOptions?.DaprListenAddresses), + NamedArg("--enable-api-logging", sidecarOptions?.EnableApiLogging), + NamedArg("--enable-app-health-check", sidecarOptions?.EnableAppHealthCheck), + NamedArg("--enable-profiling", sidecarOptions?.EnableProfiling), + NamedArg("--log-level", sidecarOptions?.LogLevel), + NamedArg("--placement-host-address", sidecarOptions?.PlacementHostAddress), + NamedArg("--resources-path", sidecarOptions?.ResourcesPaths), + NamedArg("--run-file", sidecarOptions?.RunFile), + NamedArg("--unix-domain-socket", sidecarOptions?.UnixDomainSocket), + PostOptionsArgs(Args(sidecarOptions?.Command))); + + // + // NOTE: Use custom port allocator for unspecified ports until DCP supports executable command line port templates. + // + + Dictionary ArgsBuilder, string? PortEnvVar)> ports = new() + { + { "grpc", (sidecarOptions?.DaprGrpcPort ?? this._portManager.ReservePort(DaprHttpPortStartRange), daprGrpcPortArg, "DAPR_GRPC_PORT") }, + { "http", (sidecarOptions?.DaprHttpPort ?? this._portManager.ReservePort(DaprHttpPortStartRange), daprHttpPortArg, "DAPR_HTTP_PORT") }, + { "metrics", (sidecarOptions?.MetricsPort ?? this._portManager.ReservePort(DaprHttpPortStartRange), daprMetricsPortArg, null) } + }; + + if (sidecarOptions?.EnableProfiling == true) + { + ports.Add("profile", (sidecarOptions?.ProfilePort ?? this._portManager.ReservePort(DaprHttpPortStartRange), daprProfilePortArg, null)); + } + + if (!(sidecarOptions?.AppId is { } appId)) + { + throw new DistributedApplicationException("AppId is required for Dapr sidecar executable."); + } + + var resource = new ExecutableResource(appId, fileName, workingDirectory, daprCommandLine.Arguments.ToArray()); + + project.Annotations.Add( + new EnvironmentCallbackAnnotation( + env => + { + if (resource.TryGetAllocatedEndPoints(out var endPoints)) + { + foreach (var endPoint in endPoints) + { + if (ports.TryGetValue(endPoint.Name, out var value) && value.PortEnvVar is not null) + { + env.TryAdd(value.PortEnvVar, endPoint.Port.ToString(CultureInfo.InvariantCulture)); + } + } + } + })); + + resource.Annotations.AddRange(ports.Select(port => new ServiceBindingAnnotation(ProtocolType.Tcp, name: port.Key, port: port.Value.Port))); + + // NOTE: Telemetry is enabled by default. + if (this._options.EnableTelemetry != false) + { + OtlpConfigurationExtensions.AddOtlpEnvironment(resource, _configuration, _environment); + } + + resource.Annotations.Add( + new ExecutableArgsCallbackAnnotation( + updatedArgs => + { + if (project.TryGetAllocatedEndPoints(out var projectEndPoints)) + { + var httpEndPoint = projectEndPoints.FirstOrDefault(endPoint => endPoint.Name == "http"); + + if (httpEndPoint is not null && sidecarOptions?.AppPort is null) + { + updatedArgs.AddRange(daprAppPortArg(httpEndPoint.Port)()); + } + } + + foreach (var port in ports) + { + updatedArgs.AddRange(port.Value.ArgsBuilder(port.Value.Port)()); + } + })); + + appModel.Resources.Add(resource); + } + + return Task.CompletedTask; + } +} + +internal static class IListExtensions +{ + public static void AddRange(this IList list, IEnumerable collection) + { + foreach (var item in collection) + { + list.Add(item); + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/DaprOptions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/DaprOptions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/DaprOptions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/DaprOptions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.Dapr; + +/// +/// Options for configuring Dapr. +/// +public sealed record DaprOptions +{ + /// + /// Gets or sets the path to the Dapr CLI. + /// + public string? DaprPath { get; init; } + + /// + /// Gets or sets whether Dapr sidecars export telemetry to the Aspire dashboard. + /// + /// + /// Telemetry is enabled by default. + /// + public bool? EnableTelemetry { get; init; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/DaprPortManager.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/DaprPortManager.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/DaprPortManager.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/DaprPortManager.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using System.Net.NetworkInformation; + +namespace Aspire.Hosting.Dapr; + +internal sealed class DaprPortManager +{ + private IImmutableSet _reservedPorts = ImmutableHashSet.Empty; + private readonly object _reservationLock = new(); + + public int ReservePort(int rangeStart) + { + lock (this._reservationLock) + { + var globalProperties = IPGlobalProperties.GetIPGlobalProperties(); + + var activePorts = + globalProperties + .GetActiveTcpListeners() + .Select(endPoint => endPoint.Port) + .ToImmutableHashSet(); + + var availablePort = + Enumerable + .Range(rangeStart, Int32.MaxValue - rangeStart + 1) + .Where(port => !activePorts.Contains(port)) + .Where(port => !this._reservedPorts.Contains(port)) + .FirstOrDefault(); + + if (availablePort is 0) + { + throw new InvalidOperationException("No available ports could be found."); + } + + this._reservedPorts = this._reservedPorts.Add(availablePort); + + return availablePort; + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/DaprSidecarAnnotation.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/DaprSidecarAnnotation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/DaprSidecarAnnotation.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/DaprSidecarAnnotation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; + +namespace Aspire.Hosting.Dapr; + +/// +/// Indicates that a Dapr sidecar should be started for the associated resource. +/// +public sealed record DaprSidecarAnnotation : IResourceAnnotation +{ + /// + /// Gets or sets the options used to configured the Dapr sidecar. + /// + public DaprSidecarOptions? Options { get; init; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/DaprSidecarOptions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/DaprSidecarOptions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/DaprSidecarOptions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/DaprSidecarOptions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; + +namespace Aspire.Hosting.Dapr; + +/// +/// Options for configuring a Dapr sidecar. +/// +public sealed record DaprSidecarOptions +{ + /// + /// Gets or sets the network address at which the application listens. + /// + public string? AppChannelAddress { get; init; } + + /// + /// Gets or sets the path used for health checks (HTTP only). + /// + public string? AppHealthCheckPath { get; init; } + + /// + /// Gets or sets the interval, in seconds, to probe for the health of the application. + /// + public int? AppHealthProbeInterval { get; init; } + + /// + /// Gets or sets the timeout, in milliseconds, for application health probes. + /// + public int? AppHealthProbeTimeout { get; init; } + + /// + /// Gets or sets the number of consecutive failures for the application to be considered unhealthy. + /// + public int? AppHealthThreshold { get; init; } + + /// + /// Gets or sets the ID for the application, used for service discovery. + /// + public string? AppId { get; init; } + + /// + /// Gets or sets the concurrency level of the application (unlimited if omitted). + /// + public int? AppMaxConcurrency { get; init; } + + /// + /// Gets or sets the port on which the application is listening. + /// + public int? AppPort { get; init; } + + /// + /// Gets or sets the protocol (i.e. grpc, grpcs, http, https, h2c) the Dapr sidecar uses to talk to the application. + /// + public string? AppProtocol { get; init; } + + /// + /// Gets or sets the command run by the Dapr CLI as part of starting the sidecar. + /// + public IImmutableList Command { get; init; } = ImmutableList.Empty; + + /// + /// Gets or sets the path to the Dapr sidecar configuration file. + /// + public string? Config { get; init; } + + /// + /// Gets or sets the gRPC port on which the Dapr sidecar should listen. + /// + public int? DaprGrpcPort { get; init; } + + /// + /// Gets or sets the maximum size, in MB, of a Dapr request body. + /// + public int? DaprHttpMaxRequestSize { get; init; } + + /// + /// Gets or sets the HTTP port on which the Dapr sidecard should listen. + /// + public int? DaprHttpPort { get; init; } + + /// + /// Gets or sets the maximum size, in KB, of the HTTP header read buffer. + /// + public int? DaprHttpReadBufferSize { get; init; } + + /// + /// Gets or sets the gRPC port on which the Dapr sidecar should listen for sidecar-to-sidecar calls. + /// + public int? DaprInternalGrpcPort { get; init; } + + /// + /// Gets or sets a comma (,) delimited list of IP addresses at which the Dapr sidecar will listen. + /// + public string? DaprListenAddresses { get; init; } + + /// + /// Gets or sets whether the Dapr sidecar logs API calls at INFO verbosity. + /// + public bool? EnableApiLogging { get; init; } + + /// + /// Gets or sets whether health checks are performed for the application. + /// + public bool? EnableAppHealthCheck { get; init; } + + /// + /// Gets or sets whether to perform pprof profiling via the application HTTP endpoint. + /// + public bool? EnableProfiling { get; init; } + + /// + /// Gets or sets the Dapr sidecar log verbosity (i.e. debug, info, warn, error, fatal, or panic). + /// + /// + /// The default log verbosity is "info". + /// + public string? LogLevel { get; init; } + + /// + /// Gets or sets the port on which the Dapr sidecar reports metrics. + /// + public int? MetricsPort { get; init; } + + /// + /// Gets or sets the address of the placement service. + /// + /// + /// The format is either "hostname" for the default port or "hostname:port" for a custom port. + /// The default is "localhost". + /// + public string? PlacementHostAddress { get; init; } + + /// + /// Gets or sets the port on which the Dapr sidecar reports profiling data. + /// + public int? ProfilePort { get; init; } + + /// + /// Gets or sets the paths of Dapr sidecar resources (i.e. resources). + /// + public IImmutableSet ResourcesPaths { get; init; } = ImmutableHashSet.Empty; + + /// + /// Gets or sets the path to the Dapr run file to run. + /// + public string? RunFile { get; init; } + + /// + /// Gets or sets the path to a Unix Domain Socket (UDS) directory. + /// + /// + /// If specified, the Dapr sidecar will use Unix Domain Sockets for API calls. + /// + public string? UnixDomainSocket { get; init; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/IDistributedApplicationBuilderExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/IDistributedApplicationBuilderExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/IDistributedApplicationBuilderExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/IDistributedApplicationBuilderExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.Dapr; +using Aspire.Hosting.Lifecycle; +using Microsoft.Extensions.DependencyInjection; + +namespace Aspire.Hosting; + +/// +/// Extensions to related to Dapr. +/// +public static class IDistributedApplicationBuilderExtensions +{ + /// + /// Adds Dapr support to Aspire, including the ability to add Dapr sidecar to application resource. + /// + /// The distributed application builder instance. + /// Options for configuring Dapr, if any. + /// The distributed application builder instance. + public static IDistributedApplicationBuilder AddDapr(this IDistributedApplicationBuilder builder, DaprOptions? options = null) + { + builder.Services.AddSingleton(options ?? new DaprOptions()); + builder.Services.AddLifecycleHook(); + builder.Services.AddSingleton(); + + return builder; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/IDistributedApplicationComponentBuilderExtensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/IDistributedApplicationComponentBuilderExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Dapr/IDistributedApplicationComponentBuilderExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Dapr/IDistributedApplicationComponentBuilderExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Dapr; + +namespace Aspire.Hosting; + +/// +/// Extensions to related to Dapr. +/// +public static class IDistributedApplicationResourceBuilderExtensions +{ + /// + /// Ensures that a Dapr sidecar is started for the resource. + /// + /// The type of the resource. + /// The resource builder instance. + /// The ID for the application, used for service discovery. + /// The resource builder instance. + public static IResourceBuilder WithDaprSidecar(this IResourceBuilder builder, string appId) where T : IResource + { + return builder.WithDaprSidecar(new DaprSidecarOptions { AppId = appId }); + } + + /// + /// Ensures that a Dapr sidecar is started for the resource. + /// + /// The type of the resource. + /// The resource builder instance. + /// Options for configuring the Dapr sidecar, if any. + /// The resource builder instance. + public static IResourceBuilder WithDaprSidecar(this IResourceBuilder builder, DaprSidecarOptions? options = null) where T : IResource + { + builder.WithAnnotation(new DaprSidecarAnnotation { Options = options }); + + return builder; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Sdk/Aspire.Hosting.Sdk.csproj dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Sdk/Aspire.Hosting.Sdk.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Sdk/Aspire.Hosting.Sdk.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Sdk/Aspire.Hosting.Sdk.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,15 @@ + + + + + + .NET Aspire Hosting SDK. Enabled via <IsAspireHost>true</IsAspireHost>. + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Sdk/SDK/AutoImport.props dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Sdk/SDK/AutoImport.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Sdk/SDK/AutoImport.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Sdk/SDK/AutoImport.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1 @@ + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Sdk/SDK/Sdk.props dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Sdk/SDK/Sdk.props --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Sdk/SDK/Sdk.props 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Sdk/SDK/Sdk.props 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1 @@ + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Sdk/SDK/Sdk.targets dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Sdk/SDK/Sdk.targets --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.Hosting.Sdk/SDK/Sdk.targets 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.Hosting.Sdk/SDK/Sdk.targets 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + false + false + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/Aspire.ProjectTemplates.csproj dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/Aspire.ProjectTemplates.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/Aspire.ProjectTemplates.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/Aspire.ProjectTemplates.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,61 @@ + + + + $(NetCurrent) + Library + true + true + false + content + Template + $(NoWarn);NU5128 + false + + + + + + + + <_TemplatesForPackage Include="$(IntermediateOutputPath)\content\templates\**\*" /> + + + + + + + + + + + + + $(IntermediateOutputPath)\content\templates\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + + + + + <_ContentFilesToPackage Include="templates\**\*" Exclude="templates\**\bin\**;templates\**\obj\**;templates\**\*.csproj" /> + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/appsettings.Development.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/appsettings.Development.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/appsettings.Development.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/appsettings.Development.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/appsettings.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/appsettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/AspireApplication-1.AppHost.csproj dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/AspireApplication-1.AppHost.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/AspireApplication-1.AppHost.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/AspireApplication-1.AppHost.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + true + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/Program.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/Program.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,3 @@ +var builder = DistributedApplication.CreateBuilder(args); + +builder.Build().Run(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/Properties/launchSettings.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/Properties/launchSettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/Properties/launchSettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.AppHost/Properties/launchSettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16000" + } + //#if (HasHttpsProfile) + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17000;http://localhost:15000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:18000" + } + } + //#else + } + //#endif + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.ServiceDefaults/AspireApplication-1.ServiceDefaults.csproj dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.ServiceDefaults/AspireApplication-1.ServiceDefaults.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.ServiceDefaults/AspireApplication-1.ServiceDefaults.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.ServiceDefaults/AspireApplication-1.ServiceDefaults.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,24 @@ + + + + Library + net8.0 + enable + enable + true + + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.ServiceDefaults/Extensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.ServiceDefaults/Extensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.ServiceDefaults/Extensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.ServiceDefaults/Extensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,119 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Logs; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +public static class Extensions +{ + public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder) + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.UseServiceDiscovery(); + }); + + return builder; + } + + public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder) + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddRuntimeInstrumentation() + .AddBuiltInMeters(); + }) + .WithTracing(tracing => + { + if (builder.Environment.IsDevelopment()) + { + // We want to view all traces in development + tracing.SetSampler(new AlwaysOnSampler()); + } + + tracing.AddAspNetCoreInstrumentation() + .AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder) + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.Configure(logging => logging.AddOtlpExporter()); + builder.Services.ConfigureOpenTelemetryMeterProvider(metrics => metrics.AddOtlpExporter()); + builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter()); + } + + // Uncomment the following lines to enable the Prometheus exporter (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package) + // builder.Services.AddOpenTelemetry() + // .WithMetrics(metrics => metrics.AddPrometheusExporter()); + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.Exporter package) + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + + return builder; + } + + public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder) + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Uncomment the following line to enable the Prometheus endpoint (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package) + // app.MapPrometheusScrapingEndpoint(); + + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/readiness"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + + return app; + } + + private static MeterProviderBuilder AddBuiltInMeters(this MeterProviderBuilder meterProviderBuilder) => + meterProviderBuilder.AddMeter( + "Microsoft.AspNetCore.Hosting", + "Microsoft.AspNetCore.Server.Kestrel", + "System.Net.Http"); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.sln dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.sln --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.sln 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/AspireApplication-1.sln 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.0.0 +MinimumVisualStudioVersion = 17.8.0.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspireApplication-1.AppHost", "AspireApplication-1.AppHost\AspireApplication-1.AppHost.csproj", "{F98A6C4E-E01C-44BB-BCC9-4C23F1CD09CD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspireApplication-1.ServiceDefaults", "AspireApplication-1.ServiceDefaults\AspireApplication-1.ServiceDefaults.csproj", "{8CD1957F-C0E5-454E-8BDC-88F84DD58303}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F98A6C4E-E01C-44BB-BCC9-4C23F1CD09CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F98A6C4E-E01C-44BB-BCC9-4C23F1CD09CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F98A6C4E-E01C-44BB-BCC9-4C23F1CD09CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F98A6C4E-E01C-44BB-BCC9-4C23F1CD09CD}.Release|Any CPU.Build.0 = Release|Any CPU + {8CD1957F-C0E5-454E-8BDC-88F84DD58303}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CD1957F-C0E5-454E-8BDC-88F84DD58303}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CD1957F-C0E5-454E-8BDC-88F84DD58303}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CD1957F-C0E5-454E-8BDC-88F84DD58303}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1C1883BC-2DC4-4D49-82A6-9909F00D385D} + EndGlobalSection +EndGlobal \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/.template.config/dotnetcli.host.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/.template.config/dotnetcli.host.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/.template.config/dotnetcli.host.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/.template.config/dotnetcli.host.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,23 @@ +{ + "$schema": "http://json.schemastore.org/dotnetcli.host", + "symbolInfo": { + "Framework": { + "longName": "framework" + }, + "skipRestore": { + "longName": "no-restore", + "shortName": "" + }, + "appHttpPort": { + "isHidden": true + }, + "appHttpsPort": { + "isHidden": true + }, + "NoHttps": { + "longName": "no-https", + "shortName": "" + } + }, + "usageExamples": [ ] + } \ No newline at end of file Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/.template.config/ide/AspireEmpty.ico and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/.template.config/ide/AspireEmpty.ico differ diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/.template.config/ide.host.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/.template.config/ide.host.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/.template.config/ide.host.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/.template.config/ide.host.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,19 @@ +{ + "$schema": "https://json.schemastore.org/ide.host", + "icon": "ide/AspireEmpty.ico", + "displayOverviewPage": "0", + "disableHttpsSymbol": "NoHttps", + "unsupportedHosts": [ + { + "id": "vs", + "version": "(,17.9)" + } + ], + "requiredComponents": [ + { + "hostId": "vs", + "componentType": "setupComponent", + "id": "aspire" + } + ] +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/.template.config/template.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/.template.config/template.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/.template.config/template.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-empty/.template.config/template.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,199 @@ +{ + "$schema": "https://json.schemastore.org/template", + "author": "Microsoft", + "classifications": [ + "Common", + "Aspire", + "Cloud" + ], + "name": "Aspire Application", + "defaultName": "AspireApp", + "description": "A project template for creating an empty Aspire app.", + "shortName": "aspire", + "sourceName": "AspireApplication-1", + "preferNameDirectory": true, + "tags": { + "language": "C#", + "type": "solution", + "editorTreatAs": "solution" + }, + "precedence": "8000", + "identity": "Aspire.Empty.CSharp.8.0", + "thirdPartyNotices": "https://aka.ms/aspnetcore/8.0-third-party-notices", + "groupIdentity": "Aspire.Empty", + "guids": [ + "F98A6C4E-E01C-44BB-BCC9-4C23F1CD09CD", + "8CD1957F-C0E5-454E-8BDC-88F84DD58303", + "1C1883BC-2DC4-4D49-82A6-9909F00D385D" + ], + "sources": [ + { + "modifiers": [ + { + "condition": "(hostIdentifier == \"vs\")", + "exclude": [ + "*.sln" + ] + } + ] + } + ], + "symbols": { + "Framework": { + "type": "parameter", + "description": "The target framework for the project.", + "datatype": "choice", + "choices": [ + { + "choice": "net8.0", + "description": "Target net8.0" + } + ], + "replaces": "net8.0", + "defaultValue": "net8.0" + }, + "hostIdentifier": { + "type": "bind", + "binding": "HostIdentifier" + }, + "appHttpPort": { + "type": "parameter", + "datatype": "integer", + "description": "Port number to use for the HTTP endpoint in launchSettings.json of the App project." + }, + "appHttpPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 15000, + "high": 15300 + } + }, + "appHttpPortReplacer": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "appHttpPort", + "fallbackVariableName": "appHttpPortGenerated" + }, + "replaces": "15000" + }, + "appOtlpHttpPort": { + "type": "parameter", + "datatype": "integer", + "description": "Port number to use for the OTLP HTTP endpoint in launchSettings.json of the App project." + }, + "appOtlpHttpPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 16000, + "high": 16300 + } + }, + "appOtlpHttpPortReplacer": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "appOtlpHttpPort", + "fallbackVariableName": "appOtlpHttpPortGenerated" + }, + "replaces": "16000" + }, + "appHttpsPort": { + "type": "parameter", + "datatype": "integer", + "description": "Port number to use for the HTTPS endpoint in launchSettings.json of the App project. This option is only applicable when the parameter no-https is not used." + }, + "appHttpsPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 17000, + "high": 17300 + } + }, + "appHttpsPortReplacer": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "appHttpsPort", + "fallbackVariableName": "appHttpsPortGenerated" + }, + "replaces": "17000" + }, + "appOtlpHttpsPort": { + "type": "parameter", + "datatype": "integer", + "description": "Port number to use for the OTLP HTTPS endpoint in launchSettings.json of the App project." + }, + "appOtlpHttpsPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 18000, + "high": 18300 + } + }, + "appOtlpHttpsPortReplacer": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "appOtlpHttpsPort", + "fallbackVariableName": "appOtlpHttpsPortGenerated" + }, + "replaces": "18000" + }, + "skipRestore": { + "type": "parameter", + "datatype": "bool", + "description": "If specified, skips the automatic restore of the project on create.", + "defaultValue": "false" + }, + "HasHttpsProfile": { + "type": "computed", + "value": "(!NoHttps)" + }, + "NoHttps": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "description": "Whether to turn off HTTPS." + } + }, + "primaryOutputs": [ + { + "path": "AspireApplication-1.sln", + "condition": "(hostIdentifier != \"vs\")" + }, + { + "path": "AspireApplication-1.AppHost\\AspireApplication-1.AppHost.csproj" + }, + { + "path": "AspireApplication-1.ServiceDefaults\\AspireApplication-1.ServiceDefaults.csproj" + } + ], + "postActions": [ + { + "id": "set-startup-project", + "description": "Sets the startup project in the solution", + "actionId": "5BECCC32-4D5A-4476-A0F9-BD2E81AF0689", + "condition": "(hostIdentifier == \"vs\")", + "args": { + "projects": "0" + } + }, + { + "id": "restore", + "condition": "(!skipRestore)", + "description": "Restore NuGet packages required by this project.", + "manualInstructions": [ + { + "text": "Run 'dotnet restore'" + } + ], + "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", + "continueOnError": true + } + ] +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/appsettings.Development.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/appsettings.Development.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/appsettings.Development.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/appsettings.Development.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/appsettings.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/appsettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/AspireStarterApplication-1.ApiService.csproj dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/AspireStarterApplication-1.ApiService.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/AspireStarterApplication-1.ApiService.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/AspireStarterApplication-1.ApiService.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/Program.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/Program.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,39 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add service defaults & Aspire components. +builder.AddServiceDefaults(); + +// Add services to the container. +builder.Services.AddProblemDetails(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +app.UseExceptionHandler(); + +var summaries = new[] +{ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +}; + +app.MapGet("/weatherforecast", () => +{ + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + return forecast; +}); + +app.MapDefaultEndpoints(); + +app.Run(); + +record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/Properties/launchSettings.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/Properties/launchSettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/Properties/launchSettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ApiService/Properties/launchSettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,29 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "weatherforecast", + "applicationUrl": "http://localhost:5301", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + //#if (HasHttpsProfile) + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "weatherforecast", + "applicationUrl": "https://localhost:7301;http://localhost:5301", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + //#else + } + //#endif + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/appsettings.Development.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/appsettings.Development.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/appsettings.Development.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/appsettings.Development.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/appsettings.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/appsettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/AspireStarterApplication-1.AppHost.csproj dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/AspireStarterApplication-1.AppHost.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/AspireStarterApplication-1.AppHost.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/AspireStarterApplication-1.AppHost.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,20 @@ + + + + Exe + net8.0 + enable + enable + true + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/Program.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/Program.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,15 @@ +var builder = DistributedApplication.CreateBuilder(args); + +#if UseRedisCache +var cache = builder.AddRedisContainer("cache"); + +#endif +var apiservice = builder.AddProject("apiservice"); + +builder.AddProject("webfrontend") +#if UseRedisCache + .WithReference(cache) +#endif + .WithReference(apiservice); + +builder.Build().Run(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/Properties/launchSettings.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/Properties/launchSettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/Properties/launchSettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.AppHost/Properties/launchSettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16000" + } + //#if (HasHttpsProfile) + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17000;http://localhost:15000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:18000" + } + } + //#else + } + //#endif + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ServiceDefaults/AspireStarterApplication-1.ServiceDefaults.csproj dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ServiceDefaults/AspireStarterApplication-1.ServiceDefaults.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ServiceDefaults/AspireStarterApplication-1.ServiceDefaults.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ServiceDefaults/AspireStarterApplication-1.ServiceDefaults.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,24 @@ + + + + Library + net8.0 + enable + enable + true + + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ServiceDefaults/Extensions.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ServiceDefaults/Extensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ServiceDefaults/Extensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.ServiceDefaults/Extensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,119 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Logs; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +public static class Extensions +{ + public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder) + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.UseServiceDiscovery(); + }); + + return builder; + } + + public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder) + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddRuntimeInstrumentation() + .AddBuiltInMeters(); + }) + .WithTracing(tracing => + { + if (builder.Environment.IsDevelopment()) + { + // We want to view all traces in development + tracing.SetSampler(new AlwaysOnSampler()); + } + + tracing.AddAspNetCoreInstrumentation() + .AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder) + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.Configure(logging => logging.AddOtlpExporter()); + builder.Services.ConfigureOpenTelemetryMeterProvider(metrics => metrics.AddOtlpExporter()); + builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter()); + } + + // Uncomment the following lines to enable the Prometheus exporter (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package) + // builder.Services.AddOpenTelemetry() + // .WithMetrics(metrics => metrics.AddPrometheusExporter()); + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.Exporter package) + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + + return builder; + } + + public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder) + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Uncomment the following line to enable the Prometheus endpoint (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package) + // app.MapPrometheusScrapingEndpoint(); + + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/readiness"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + + return app; + } + + private static MeterProviderBuilder AddBuiltInMeters(this MeterProviderBuilder meterProviderBuilder) => + meterProviderBuilder.AddMeter( + "Microsoft.AspNetCore.Hosting", + "Microsoft.AspNetCore.Server.Kestrel", + "System.Net.Http"); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.sln dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.sln --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.sln 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.sln 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,42 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.0.0 +MinimumVisualStudioVersion = 17.8.0.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspireStarterApplication-1.AppHost", "AspireStarterApplication-1.AppHost\AspireStarterApplication-1.AppHost.csproj", "{80B24B1B-1E78-4FCB-BDC9-13678F1789F4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspireStarterApplication-1.ServiceDefaults", "AspireStarterApplication-1.ServiceDefaults\AspireStarterApplication-1.ServiceDefaults.csproj", "{DB7A3AC1-6E4F-4805-B710-2FCD1084E96E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspireStarterApplication-1.ApiService", "AspireStarterApplication-1.ApiService\AspireStarterApplication-1.ApiService.csproj", "{9FEB877E-015D-4E20-AE63-06C596E242E4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspireStarterApplication-1.Web", "AspireStarterApplication-1.Web\AspireStarterApplication-1.Web.csproj", "{AC2DB38C-F5AD-4CEF-BC4C-04AE6EE86C9F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {80B24B1B-1E78-4FCB-BDC9-13678F1789F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80B24B1B-1E78-4FCB-BDC9-13678F1789F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80B24B1B-1E78-4FCB-BDC9-13678F1789F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80B24B1B-1E78-4FCB-BDC9-13678F1789F4}.Release|Any CPU.Build.0 = Release|Any CPU + {DB7A3AC1-6E4F-4805-B710-2FCD1084E96E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB7A3AC1-6E4F-4805-B710-2FCD1084E96E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB7A3AC1-6E4F-4805-B710-2FCD1084E96E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB7A3AC1-6E4F-4805-B710-2FCD1084E96E}.Release|Any CPU.Build.0 = Release|Any CPU + {9FEB877E-015D-4E20-AE63-06C596E242E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FEB877E-015D-4E20-AE63-06C596E242E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FEB877E-015D-4E20-AE63-06C596E242E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FEB877E-015D-4E20-AE63-06C596E242E4}.Release|Any CPU.Build.0 = Release|Any CPU + {AC2DB38C-F5AD-4CEF-BC4C-04AE6EE86C9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC2DB38C-F5AD-4CEF-BC4C-04AE6EE86C9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC2DB38C-F5AD-4CEF-BC4C-04AE6EE86C9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC2DB38C-F5AD-4CEF-BC4C-04AE6EE86C9F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EB6E56D3-85C9-43D0-A65C-775F4C780950} + EndGlobalSection +EndGlobal \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/appsettings.Development.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/appsettings.Development.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/appsettings.Development.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/appsettings.Development.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/appsettings.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/appsettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/AspireStarterApplication-1.Web.csproj dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/AspireStarterApplication-1.Web.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/AspireStarterApplication-1.Web.csproj 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/AspireStarterApplication-1.Web.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,20 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/App.razor dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/App.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/App.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/App.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/_Imports.razor dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/_Imports.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/_Imports.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/_Imports.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.AspNetCore.OutputCaching +@using Microsoft.JSInterop +@using AspireStarterApplication_1.Web +@using AspireStarterApplication_1.Web.Components diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/MainLayout.razor dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/MainLayout.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/MainLayout.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/MainLayout.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,23 @@ +@inherits LayoutComponentBase + +
    + + +
    +
    + About +
    + +
    + @Body +
    +
    +
    + +
    + An unhandled error has occurred. + Reload + 🗙 +
    diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/MainLayout.razor.css dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/MainLayout.razor.css --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/MainLayout.razor.css 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/MainLayout.razor.css 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,96 @@ +.page { + position: relative; + display: flex; + flex-direction: column; +} + +main { + flex: 1; +} + +.sidebar { + background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +} + +.top-row { + background-color: #f7f7f7; + border-bottom: 1px solid #d6d5d5; + justify-content: flex-end; + height: 3.5rem; + display: flex; + align-items: center; +} + + .top-row ::deep a, .top-row ::deep .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + text-decoration: none; + } + + .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { + text-decoration: underline; + } + + .top-row ::deep a:first-child { + overflow: hidden; + text-overflow: ellipsis; + } + +@media (max-width: 640.98px) { + .top-row { + justify-content: space-between; + } + + .top-row ::deep a, .top-row ::deep .btn-link { + margin-left: 0; + } +} + +@media (min-width: 641px) { + .page { + flex-direction: row; + } + + .sidebar { + width: 250px; + height: 100vh; + position: sticky; + top: 0; + } + + .top-row { + position: sticky; + top: 0; + z-index: 1; + } + + .top-row.auth ::deep a:first-child { + flex: 1; + text-align: right; + width: 0; + } + + .top-row, article { + padding-left: 2rem !important; + padding-right: 1.5rem !important; + } +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/NavMenu.razor dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/NavMenu.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/NavMenu.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/NavMenu.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,29 @@ + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/NavMenu.razor.css dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/NavMenu.razor.css --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/NavMenu.razor.css 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Layout/NavMenu.razor.css 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,124 @@ +.navbar-toggler { + appearance: none; + cursor: pointer; + width: 3.5rem; + height: 2.5rem; + color: white; + position: absolute; + top: 0.5rem; + right: 1rem; + border: 1px solid rgba(255, 255, 255, 0.1); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1); +} + +.navbar-toggler:checked { + background-color: rgba(255, 255, 255, 0.5); +} + +.top-row { + height: 3.5rem; + background-color: rgba(0,0,0,0.4); +} + +.navbar-brand { + font-size: 1.1rem; +} + +.bi { + display: inline-block; + position: relative; + width: 1.25rem; + height: 1.25rem; + margin-right: 0.75rem; + top: -1px; + background-size: cover; +} + +.bi-house-door-fill { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); +} + +.bi-plus-square-fill { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); +} + +.bi-list-nested { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); +} + +/*#if (IndividualLocalAuth)*/ +.bi-lock { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath d='M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2zM5 8h6a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z'/%3E%3C/svg%3E"); +} + +.bi-person { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person' viewBox='0 0 16 16'%3E%3Cpath d='M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0Zm4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4Zm-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664h10Z'/%3E%3C/svg%3E"); +} + +.bi-person-badge { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person-badge' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-3zM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0z'/%3E%3Cpath d='M4.5 0A2.5 2.5 0 0 0 2 2.5V14a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2.5A2.5 2.5 0 0 0 11.5 0h-7zM3 2.5A1.5 1.5 0 0 1 4.5 1h7A1.5 1.5 0 0 1 13 2.5v10.795a4.2 4.2 0 0 0-.776-.492C11.392 12.387 10.063 12 8 12s-3.392.387-4.224.803a4.2 4.2 0 0 0-.776.492V2.5z'/%3E%3C/svg%3E"); +} + +.bi-person-fill { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person-fill' viewBox='0 0 16 16'%3E%3Cpath d='M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3Zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z'/%3E%3C/svg%3E"); +} + +.bi-arrow-bar-left { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-arrow-bar-left' viewBox='0 0 16 16'%3E%3Cpath d='M12.5 15a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5ZM10 8a.5.5 0 0 1-.5.5H3.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L3.707 7.5H9.5a.5.5 0 0 1 .5.5Z'/%3E%3C/svg%3E"); +} + +/*#endif*/ +.nav-item { + font-size: 0.9rem; + padding-bottom: 0.5rem; +} + + .nav-item:first-of-type { + padding-top: 1rem; + } + + .nav-item:last-of-type { + padding-bottom: 1rem; + } + + .nav-item ::deep a { + color: #d7d7d7; + border-radius: 4px; + height: 3rem; + display: flex; + align-items: center; + line-height: 3rem; + } + +.nav-item ::deep a.active { + background-color: rgba(255,255,255,0.37); + color: white; +} + +.nav-item ::deep a:hover { + background-color: rgba(255,255,255,0.1); + color: white; +} + +.nav-scrollable { + display: none; +} + +.navbar-toggler:checked ~ .nav-scrollable { + display: block; +} + +@media (min-width: 641px) { + .navbar-toggler { + display: none; + } + + .nav-scrollable { + /* Never collapse the sidebar for wide screens */ + display: block; + + /* Allow sidebar to scroll for tall menus */ + height: calc(100vh - 3.5rem); + overflow-y: auto; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Counter.razor dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Counter.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Counter.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Counter.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,19 @@ +@page "/counter" +@rendermode InteractiveServer + +Counter + +

    Counter

    + +

    Current count: @currentCount

    + + + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Error.razor dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Error.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Error.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Error.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,38 @@ +@page "/Error" +@using System.Diagnostics + +Error + +

    Error.

    +

    An error occurred while processing your request.

    + +@if (ShowRequestId) +{ +

    + Request ID: @requestId +

    +} + +

    Development Mode

    +

    + Swapping to Development environment will display more detailed information about the error that occurred. +

    +

    + The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

    + +@code{ + [CascadingParameter] + public HttpContext? HttpContext { get; set; } + + private string? requestId; + private bool ShowRequestId => !string.IsNullOrEmpty(requestId); + + protected override void OnInitialized() + { + requestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Home.razor dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Home.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Home.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Home.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,7 @@ +@page "/" + +Home + +

    Hello, world!

    + +Welcome to your new app. diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Weather.razor dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Weather.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Weather.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Pages/Weather.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,49 @@ +@page "/weather" +@attribute [StreamRendering(true)] +@attribute [OutputCache(Duration = 5)] + +@inject WeatherApiClient WeatherApi + +Weather + +

    Weather

    + +

    This component demonstrates showing data loaded from a backend API service.

    + +@if (forecasts == null) +{ +

    Loading...

    +} +else +{ + + + + + + + + + + + @foreach (var forecast in forecasts) + { + + + + + + + } + +
    DateTemp. (C)Temp. (F)Summary
    @forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
    +} + +@code { + private WeatherForecast[]? forecasts; + + protected override async Task OnInitializedAsync() + { + forecasts = await WeatherApi.GetWeatherAsync(); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Routes.razor dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Routes.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Routes.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Components/Routes.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,6 @@ + + + + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Program.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Program.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,44 @@ +using AspireStarterApplication_1.Web; +using AspireStarterApplication_1.Web.Components; + +var builder = WebApplication.CreateBuilder(args); + +// Add service defaults & Aspire components. +builder.AddServiceDefaults(); +#if (UseRedisCache) +builder.AddRedisOutputCache("cache"); +#endif + +// Add services to the container. +builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); + +#if (!UseRedisCache) +builder.Services.AddOutputCache(); + +#endif +#if (HasHttpsProfile) +builder.Services.AddHttpClient(client=> client.BaseAddress = new("https://apiservice")); +#else +builder.Services.AddHttpClient(client=> client.BaseAddress = new("http://apiservice")); +#endif + +var app = builder.Build(); + +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error", createScopeForErrors: true); +} + +app.UseStaticFiles(); + +app.UseAntiforgery(); + +app.UseOutputCache(); + +app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + +app.MapDefaultEndpoints(); + +app.Run(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Properties/launchSettings.json dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Properties/launchSettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Properties/launchSettings.json 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/Properties/launchSettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,27 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + //#if (HasHttpsProfile) + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7000;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + //#else + } + //#endif + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/WeatherApiClient.cs dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/WeatherApiClient.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/WeatherApiClient.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/WeatherApiClient.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,14 @@ +namespace AspireStarterApplication_1.Web; + +public class WeatherApiClient(HttpClient httpClient) +{ + public async Task GetWeatherAsync() + { + return await httpClient.GetFromJsonAsync("/weatherforecast") ?? []; + } +} + +public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/wwwroot/app.css dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/wwwroot/app.css --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/wwwroot/app.css 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/wwwroot/app.css 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,49 @@ +/*#if (SampleContent)*/ +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +a, .btn-link { + color: #006bb7; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; +} + +.content { + padding-top: 1.1rem; +} + +/*#endif*/ +h1:focus { + outline: none; +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid #e50000; +} + +.validation-message { + color: #e50000; +} + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/wwwroot/bootstrap/bootstrap.min.css dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/wwwroot/bootstrap/bootstrap.min.css --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/wwwroot/bootstrap/bootstrap.min.css 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/wwwroot/bootstrap/bootstrap.min.css 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,7 @@ +@charset "UTF-8";/*! + * Bootstrap v5.1.0 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-rgb:33,37,41;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/wwwroot/bootstrap/bootstrap.min.css.map dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/wwwroot/bootstrap/bootstrap.min.css.map --- dotnet8-8.0.100-8.0.0~rc2/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/wwwroot/bootstrap/bootstrap.min.css.map 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspire/src/Aspire.ProjectTemplates/templates/aspire-starter/AspireStarterApplication-1.Web/wwwroot/bootstrap/bootstrap.min.css.map 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1 @@ +{"version":3,"sources":["../../scss/bootstrap.scss","../../scss/_root.scss","../../scss/_reboot.scss","dist/css/bootstrap.css","../../scss/vendor/_rfs.scss","../../scss/mixins/_border-radius.scss","../../scss/_type.scss","../../scss/mixins/_lists.scss","../../scss/_images.scss","../../scss/mixins/_image.scss","../../scss/_containers.scss","../../scss/mixins/_container.scss","../../scss/mixins/_breakpoints.scss","../../scss/_grid.scss","../../scss/mixins/_grid.scss","../../scss/_tables.scss","../../scss/mixins/_table-variants.scss","../../scss/forms/_labels.scss","../../scss/forms/_form-text.scss","../../scss/forms/_form-control.scss","../../scss/mixins/_transition.scss","../../scss/mixins/_gradients.scss","../../scss/forms/_form-select.scss","../../scss/forms/_form-check.scss","../../scss/forms/_form-range.scss","../../scss/forms/_floating-labels.scss","../../scss/forms/_input-group.scss","../../scss/mixins/_forms.scss","../../scss/_buttons.scss","../../scss/mixins/_buttons.scss","../../scss/_transitions.scss","../../scss/_dropdown.scss","../../scss/mixins/_caret.scss","../../scss/_button-group.scss","../../scss/_nav.scss","../../scss/_navbar.scss","../../scss/_card.scss","../../scss/_accordion.scss","../../scss/_breadcrumb.scss","../../scss/_pagination.scss","../../scss/mixins/_pagination.scss","../../scss/_badge.scss","../../scss/_alert.scss","../../scss/mixins/_alert.scss","../../scss/_progress.scss","../../scss/_list-group.scss","../../scss/mixins/_list-group.scss","../../scss/_close.scss","../../scss/_toasts.scss","../../scss/_modal.scss","../../scss/mixins/_backdrop.scss","../../scss/_tooltip.scss","../../scss/mixins/_reset-text.scss","../../scss/_popover.scss","../../scss/_carousel.scss","../../scss/mixins/_clearfix.scss","../../scss/_spinners.scss","../../scss/_offcanvas.scss","../../scss/_placeholders.scss","../../scss/helpers/_colored-links.scss","../../scss/helpers/_ratio.scss","../../scss/helpers/_position.scss","../../scss/helpers/_stacks.scss","../../scss/helpers/_visually-hidden.scss","../../scss/mixins/_visually-hidden.scss","../../scss/helpers/_stretched-link.scss","../../scss/helpers/_text-truncation.scss","../../scss/mixins/_text-truncate.scss","../../scss/helpers/_vr.scss","../../scss/mixins/_utilities.scss","../../scss/utilities/_api.scss"],"names":[],"mappings":"iBAAA;;;;;ACAA,MAQI,UAAA,QAAA,YAAA,QAAA,YAAA,QAAA,UAAA,QAAA,SAAA,QAAA,YAAA,QAAA,YAAA,QAAA,WAAA,QAAA,UAAA,QAAA,UAAA,QAAA,WAAA,KAAA,UAAA,QAAA,eAAA,QAIA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAIA,aAAA,QAAA,eAAA,QAAA,aAAA,QAAA,UAAA,QAAA,aAAA,QAAA,YAAA,QAAA,WAAA,QAAA,UAAA,QAIA,iBAAA,EAAA,CAAA,GAAA,CAAA,IAAA,mBAAA,GAAA,CAAA,GAAA,CAAA,IAAA,iBAAA,EAAA,CAAA,GAAA,CAAA,GAAA,cAAA,EAAA,CAAA,GAAA,CAAA,IAAA,iBAAA,GAAA,CAAA,GAAA,CAAA,EAAA,gBAAA,GAAA,CAAA,EAAA,CAAA,GAAA,eAAA,GAAA,CAAA,GAAA,CAAA,IAAA,cAAA,EAAA,CAAA,EAAA,CAAA,GAGF,eAAA,GAAA,CAAA,GAAA,CAAA,IACA,eAAA,CAAA,CAAA,CAAA,CAAA,EACA,cAAA,EAAA,CAAA,EAAA,CAAA,GAMA,qBAAA,SAAA,CAAA,aAAA,CAAA,UAAA,CAAA,MAAA,CAAA,gBAAA,CAAA,KAAA,CAAA,WAAA,CAAA,iBAAA,CAAA,UAAA,CAAA,mBAAA,CAAA,gBAAA,CAAA,iBAAA,CAAA,mBACA,oBAAA,cAAA,CAAA,KAAA,CAAA,MAAA,CAAA,QAAA,CAAA,iBAAA,CAAA,aAAA,CAAA,UACA,cAAA,2EAQA,sBAAA,0BACA,oBAAA,KACA,sBAAA,IACA,sBAAA,IACA,gBAAA,QAIA,aAAA,KClCF,EC+CA,QADA,SD3CE,WAAA,WAeE,8CANJ,MAOM,gBAAA,QAcN,KACE,OAAA,EACA,YAAA,2BEmPI,UAAA,yBFjPJ,YAAA,2BACA,YAAA,2BACA,MAAA,qBACA,WAAA,0BACA,iBAAA,kBACA,yBAAA,KACA,4BAAA,YAUF,GACE,OAAA,KAAA,EACA,MAAA,QACA,iBAAA,aACA,OAAA,EACA,QAAA,IAGF,eACE,OAAA,IAUF,IAAA,IAAA,IAAA,IAAA,IAAA,IAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GACE,WAAA,EACA,cAAA,MAGA,YAAA,IACA,YAAA,IAIF,IAAA,GEwMQ,UAAA,uBAlKJ,0BFtCJ,IAAA,GE+MQ,UAAA,QF1MR,IAAA,GEmMQ,UAAA,sBAlKJ,0BFjCJ,IAAA,GE0MQ,UAAA,MFrMR,IAAA,GE8LQ,UAAA,oBAlKJ,0BF5BJ,IAAA,GEqMQ,UAAA,SFhMR,IAAA,GEyLQ,UAAA,sBAlKJ,0BFvBJ,IAAA,GEgMQ,UAAA,QF3LR,IAAA,GEgLM,UAAA,QF3KN,IAAA,GE2KM,UAAA,KFhKN,EACE,WAAA,EACA,cAAA,KCmBF,6BDRA,YAEE,wBAAA,UAAA,OAAA,gBAAA,UAAA,OACA,OAAA,KACA,iCAAA,KAAA,yBAAA,KAMF,QACE,cAAA,KACA,WAAA,OACA,YAAA,QAMF,GCIA,GDFE,aAAA,KCQF,GDLA,GCIA,GDDE,WAAA,EACA,cAAA,KAGF,MCKA,MACA,MAFA,MDAE,cAAA,EAGF,GACE,YAAA,IAKF,GACE,cAAA,MACA,YAAA,EAMF,WACE,OAAA,EAAA,EAAA,KAQF,ECNA,ODQE,YAAA,OAQF,OAAA,ME4EM,UAAA,OFrEN,MAAA,KACE,QAAA,KACA,iBAAA,QASF,ICpBA,IDsBE,SAAA,SEwDI,UAAA,MFtDJ,YAAA,EACA,eAAA,SAGF,IAAM,OAAA,OACN,IAAM,IAAA,MAKN,EACE,MAAA,QACA,gBAAA,UAEA,QACE,MAAA,QAWF,2BAAA,iCAEE,MAAA,QACA,gBAAA,KCxBJ,KACA,ID8BA,IC7BA,KDiCE,YAAA,yBEcI,UAAA,IFZJ,UAAA,IACA,aAAA,cAOF,IACE,QAAA,MACA,WAAA,EACA,cAAA,KACA,SAAA,KEAI,UAAA,OFKJ,SELI,UAAA,QFOF,MAAA,QACA,WAAA,OAIJ,KEZM,UAAA,OFcJ,MAAA,QACA,UAAA,WAGA,OACE,MAAA,QAIJ,IACE,QAAA,MAAA,MExBI,UAAA,OF0BJ,MAAA,KACA,iBAAA,QG7SE,cAAA,MHgTF,QACE,QAAA,EE/BE,UAAA,IFiCF,YAAA,IASJ,OACE,OAAA,EAAA,EAAA,KAMF,ICjDA,IDmDE,eAAA,OAQF,MACE,aAAA,OACA,gBAAA,SAGF,QACE,YAAA,MACA,eAAA,MACA,MAAA,QACA,WAAA,KAOF,GAEE,WAAA,QACA,WAAA,qBCxDF,MAGA,GAFA,MAGA,GDuDA,MCzDA,GD+DE,aAAA,QACA,aAAA,MACA,aAAA,EAQF,MACE,QAAA,aAMF,OAEE,cAAA,EAQF,iCACE,QAAA,ECtEF,OD2EA,MCzEA,SADA,OAEA,SD6EE,OAAA,EACA,YAAA,QE9HI,UAAA,QFgIJ,YAAA,QAIF,OC5EA,OD8EE,eAAA,KAKF,cACE,OAAA,QAGF,OAGE,UAAA,OAGA,gBACE,QAAA,EAOJ,0CACE,QAAA,KClFF,cACA,aACA,cDwFA,OAIE,mBAAA,OCxFF,6BACA,4BACA,6BDyFI,sBACE,OAAA,QAON,mBACE,QAAA,EACA,aAAA,KAKF,SACE,OAAA,SAUF,SACE,UAAA,EACA,QAAA,EACA,OAAA,EACA,OAAA,EAQF,OACE,MAAA,KACA,MAAA,KACA,QAAA,EACA,cAAA,MEnNM,UAAA,sBFsNN,YAAA,QExXE,0BFiXJ,OExMQ,UAAA,QFiNN,SACE,MAAA,KChGJ,kCDuGA,uCCxGA,mCADA,+BAGA,oCAJA,6BAKA,mCD4GE,QAAA,EAGF,4BACE,OAAA,KASF,cACE,eAAA,KACA,mBAAA,UAmBF,4BACE,mBAAA,KAKF,+BACE,QAAA,EAMF,uBACE,KAAA,QAMF,6BACE,KAAA,QACA,mBAAA,OAKF,OACE,QAAA,aAKF,OACE,OAAA,EAOF,QACE,QAAA,UACA,OAAA,QAQF,SACE,eAAA,SAQF,SACE,QAAA,eInlBF,MFyQM,UAAA,QEvQJ,YAAA,IAKA,WFsQM,UAAA,uBEpQJ,YAAA,IACA,YAAA,IFiGA,0BEpGF,WF6QM,UAAA,ME7QN,WFsQM,UAAA,uBEpQJ,YAAA,IACA,YAAA,IFiGA,0BEpGF,WF6QM,UAAA,QE7QN,WFsQM,UAAA,uBEpQJ,YAAA,IACA,YAAA,IFiGA,0BEpGF,WF6QM,UAAA,ME7QN,WFsQM,UAAA,uBEpQJ,YAAA,IACA,YAAA,IFiGA,0BEpGF,WF6QM,UAAA,QE7QN,WFsQM,UAAA,uBEpQJ,YAAA,IACA,YAAA,IFiGA,0BEpGF,WF6QM,UAAA,ME7QN,WFsQM,UAAA,uBEpQJ,YAAA,IACA,YAAA,IFiGA,0BEpGF,WF6QM,UAAA,QEvPR,eCrDE,aAAA,EACA,WAAA,KDyDF,aC1DE,aAAA,EACA,WAAA,KD4DF,kBACE,QAAA,aAEA,mCACE,aAAA,MAUJ,YFsNM,UAAA,OEpNJ,eAAA,UAIF,YACE,cAAA,KF+MI,UAAA,QE5MJ,wBACE,cAAA,EAIJ,mBACE,WAAA,MACA,cAAA,KFqMI,UAAA,OEnMJ,MAAA,QAEA,2BACE,QAAA,KE9FJ,WCIE,UAAA,KAGA,OAAA,KDDF,eACE,QAAA,OACA,iBAAA,KACA,OAAA,IAAA,MAAA,QHGE,cAAA,OIRF,UAAA,KAGA,OAAA,KDcF,QAEE,QAAA,aAGF,YACE,cAAA,MACA,YAAA,EAGF,gBJ+PM,UAAA,OI7PJ,MAAA,QElCA,WPqmBF,iBAGA,cACA,cACA,cAHA,cADA,eQzmBE,MAAA,KACA,cAAA,0BACA,aAAA,0BACA,aAAA,KACA,YAAA,KCwDE,yBF5CE,WAAA,cACE,UAAA,OE2CJ,yBF5CE,WAAA,cAAA,cACE,UAAA,OE2CJ,yBF5CE,WAAA,cAAA,cAAA,cACE,UAAA,OE2CJ,0BF5CE,WAAA,cAAA,cAAA,cAAA,cACE,UAAA,QE2CJ,0BF5CE,WAAA,cAAA,cAAA,cAAA,cAAA,eACE,UAAA,QGfN,KCAA,cAAA,OACA,cAAA,EACA,QAAA,KACA,UAAA,KACA,WAAA,8BACA,aAAA,+BACA,YAAA,+BDHE,OCYF,YAAA,EACA,MAAA,KACA,UAAA,KACA,cAAA,8BACA,aAAA,8BACA,WAAA,mBA+CI,KACE,KAAA,EAAA,EAAA,GAGF,iBApCJ,KAAA,EAAA,EAAA,KACA,MAAA,KAcA,cACE,KAAA,EAAA,EAAA,KACA,MAAA,KAFF,cACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,cACE,KAAA,EAAA,EAAA,KACA,MAAA,eAFF,cACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,cACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,cACE,KAAA,EAAA,EAAA,KACA,MAAA,eA+BE,UAhDJ,KAAA,EAAA,EAAA,KACA,MAAA,KAqDQ,OAhEN,KAAA,EAAA,EAAA,KACA,MAAA,YA+DM,OAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,OAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,OAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,OAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,OAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,OAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,OAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,OAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,QAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,QAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,QAhEN,KAAA,EAAA,EAAA,KACA,MAAA,KAuEQ,UAxDV,YAAA,YAwDU,UAxDV,YAAA,aAwDU,UAxDV,YAAA,IAwDU,UAxDV,YAAA,aAwDU,UAxDV,YAAA,aAwDU,UAxDV,YAAA,IAwDU,UAxDV,YAAA,aAwDU,UAxDV,YAAA,aAwDU,UAxDV,YAAA,IAwDU,WAxDV,YAAA,aAwDU,WAxDV,YAAA,aAmEM,KXusBR,MWrsBU,cAAA,EAGF,KXusBR,MWrsBU,cAAA,EAPF,KXitBR,MW/sBU,cAAA,QAGF,KXitBR,MW/sBU,cAAA,QAPF,KX2tBR,MWztBU,cAAA,OAGF,KX2tBR,MWztBU,cAAA,OAPF,KXquBR,MWnuBU,cAAA,KAGF,KXquBR,MWnuBU,cAAA,KAPF,KX+uBR,MW7uBU,cAAA,OAGF,KX+uBR,MW7uBU,cAAA,OAPF,KXyvBR,MWvvBU,cAAA,KAGF,KXyvBR,MWvvBU,cAAA,KFzDN,yBESE,QACE,KAAA,EAAA,EAAA,GAGF,oBApCJ,KAAA,EAAA,EAAA,KACA,MAAA,KAcA,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,KAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,eAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,eA+BE,aAhDJ,KAAA,EAAA,EAAA,KACA,MAAA,KAqDQ,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,YA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,KAuEQ,aAxDV,YAAA,EAwDU,aAxDV,YAAA,YAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,IAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,IAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,IAwDU,cAxDV,YAAA,aAwDU,cAxDV,YAAA,aAmEM,QX45BR,SW15BU,cAAA,EAGF,QX45BR,SW15BU,cAAA,EAPF,QXs6BR,SWp6BU,cAAA,QAGF,QXs6BR,SWp6BU,cAAA,QAPF,QXg7BR,SW96BU,cAAA,OAGF,QXg7BR,SW96BU,cAAA,OAPF,QX07BR,SWx7BU,cAAA,KAGF,QX07BR,SWx7BU,cAAA,KAPF,QXo8BR,SWl8BU,cAAA,OAGF,QXo8BR,SWl8BU,cAAA,OAPF,QX88BR,SW58BU,cAAA,KAGF,QX88BR,SW58BU,cAAA,MFzDN,yBESE,QACE,KAAA,EAAA,EAAA,GAGF,oBApCJ,KAAA,EAAA,EAAA,KACA,MAAA,KAcA,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,KAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,eAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,eA+BE,aAhDJ,KAAA,EAAA,EAAA,KACA,MAAA,KAqDQ,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,YA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,KAuEQ,aAxDV,YAAA,EAwDU,aAxDV,YAAA,YAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,IAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,IAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,IAwDU,cAxDV,YAAA,aAwDU,cAxDV,YAAA,aAmEM,QXinCR,SW/mCU,cAAA,EAGF,QXinCR,SW/mCU,cAAA,EAPF,QX2nCR,SWznCU,cAAA,QAGF,QX2nCR,SWznCU,cAAA,QAPF,QXqoCR,SWnoCU,cAAA,OAGF,QXqoCR,SWnoCU,cAAA,OAPF,QX+oCR,SW7oCU,cAAA,KAGF,QX+oCR,SW7oCU,cAAA,KAPF,QXypCR,SWvpCU,cAAA,OAGF,QXypCR,SWvpCU,cAAA,OAPF,QXmqCR,SWjqCU,cAAA,KAGF,QXmqCR,SWjqCU,cAAA,MFzDN,yBESE,QACE,KAAA,EAAA,EAAA,GAGF,oBApCJ,KAAA,EAAA,EAAA,KACA,MAAA,KAcA,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,KAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,eAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,eA+BE,aAhDJ,KAAA,EAAA,EAAA,KACA,MAAA,KAqDQ,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,YA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,KAuEQ,aAxDV,YAAA,EAwDU,aAxDV,YAAA,YAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,IAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,IAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,IAwDU,cAxDV,YAAA,aAwDU,cAxDV,YAAA,aAmEM,QXs0CR,SWp0CU,cAAA,EAGF,QXs0CR,SWp0CU,cAAA,EAPF,QXg1CR,SW90CU,cAAA,QAGF,QXg1CR,SW90CU,cAAA,QAPF,QX01CR,SWx1CU,cAAA,OAGF,QX01CR,SWx1CU,cAAA,OAPF,QXo2CR,SWl2CU,cAAA,KAGF,QXo2CR,SWl2CU,cAAA,KAPF,QX82CR,SW52CU,cAAA,OAGF,QX82CR,SW52CU,cAAA,OAPF,QXw3CR,SWt3CU,cAAA,KAGF,QXw3CR,SWt3CU,cAAA,MFzDN,0BESE,QACE,KAAA,EAAA,EAAA,GAGF,oBApCJ,KAAA,EAAA,EAAA,KACA,MAAA,KAcA,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,KAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,eAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,iBACE,KAAA,EAAA,EAAA,KACA,MAAA,eA+BE,aAhDJ,KAAA,EAAA,EAAA,KACA,MAAA,KAqDQ,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,YA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,UAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,KAuEQ,aAxDV,YAAA,EAwDU,aAxDV,YAAA,YAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,IAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,IAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,aAwDU,aAxDV,YAAA,IAwDU,cAxDV,YAAA,aAwDU,cAxDV,YAAA,aAmEM,QX2hDR,SWzhDU,cAAA,EAGF,QX2hDR,SWzhDU,cAAA,EAPF,QXqiDR,SWniDU,cAAA,QAGF,QXqiDR,SWniDU,cAAA,QAPF,QX+iDR,SW7iDU,cAAA,OAGF,QX+iDR,SW7iDU,cAAA,OAPF,QXyjDR,SWvjDU,cAAA,KAGF,QXyjDR,SWvjDU,cAAA,KAPF,QXmkDR,SWjkDU,cAAA,OAGF,QXmkDR,SWjkDU,cAAA,OAPF,QX6kDR,SW3kDU,cAAA,KAGF,QX6kDR,SW3kDU,cAAA,MFzDN,0BESE,SACE,KAAA,EAAA,EAAA,GAGF,qBApCJ,KAAA,EAAA,EAAA,KACA,MAAA,KAcA,kBACE,KAAA,EAAA,EAAA,KACA,MAAA,KAFF,kBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,kBACE,KAAA,EAAA,EAAA,KACA,MAAA,eAFF,kBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,kBACE,KAAA,EAAA,EAAA,KACA,MAAA,IAFF,kBACE,KAAA,EAAA,EAAA,KACA,MAAA,eA+BE,cAhDJ,KAAA,EAAA,EAAA,KACA,MAAA,KAqDQ,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,YA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,WAhEN,KAAA,EAAA,EAAA,KACA,MAAA,IA+DM,YAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,YAhEN,KAAA,EAAA,EAAA,KACA,MAAA,aA+DM,YAhEN,KAAA,EAAA,EAAA,KACA,MAAA,KAuEQ,cAxDV,YAAA,EAwDU,cAxDV,YAAA,YAwDU,cAxDV,YAAA,aAwDU,cAxDV,YAAA,IAwDU,cAxDV,YAAA,aAwDU,cAxDV,YAAA,aAwDU,cAxDV,YAAA,IAwDU,cAxDV,YAAA,aAwDU,cAxDV,YAAA,aAwDU,cAxDV,YAAA,IAwDU,eAxDV,YAAA,aAwDU,eAxDV,YAAA,aAmEM,SXgvDR,UW9uDU,cAAA,EAGF,SXgvDR,UW9uDU,cAAA,EAPF,SX0vDR,UWxvDU,cAAA,QAGF,SX0vDR,UWxvDU,cAAA,QAPF,SXowDR,UWlwDU,cAAA,OAGF,SXowDR,UWlwDU,cAAA,OAPF,SX8wDR,UW5wDU,cAAA,KAGF,SX8wDR,UW5wDU,cAAA,KAPF,SXwxDR,UWtxDU,cAAA,OAGF,SXwxDR,UWtxDU,cAAA,OAPF,SXkyDR,UWhyDU,cAAA,KAGF,SXkyDR,UWhyDU,cAAA,MCpHV,OACE,cAAA,YACA,qBAAA,YACA,yBAAA,QACA,sBAAA,oBACA,wBAAA,QACA,qBAAA,mBACA,uBAAA,QACA,oBAAA,qBAEA,MAAA,KACA,cAAA,KACA,MAAA,QACA,eAAA,IACA,aAAA,QAOA,yBACE,QAAA,MAAA,MACA,iBAAA,mBACA,oBAAA,IACA,WAAA,MAAA,EAAA,EAAA,EAAA,OAAA,0BAGF,aACE,eAAA,QAGF,aACE,eAAA,OAIF,uCACE,oBAAA,aASJ,aACE,aAAA,IAUA,4BACE,QAAA,OAAA,OAeF,gCACE,aAAA,IAAA,EAGA,kCACE,aAAA,EAAA,IAOJ,oCACE,oBAAA,EASF,yCACE,qBAAA,2BACA,MAAA,8BAQJ,cACE,qBAAA,0BACA,MAAA,6BAQA,4BACE,qBAAA,yBACA,MAAA,4BCxHF,eAME,cAAA,QACA,sBAAA,QACA,yBAAA,KACA,qBAAA,QACA,wBAAA,KACA,oBAAA,QACA,uBAAA,KAEA,MAAA,KACA,aAAA,QAfF,iBAME,cAAA,QACA,sBAAA,QACA,yBAAA,KACA,qBAAA,QACA,wBAAA,KACA,oBAAA,QACA,uBAAA,KAEA,MAAA,KACA,aAAA,QAfF,eAME,cAAA,QACA,sBAAA,QACA,yBAAA,KACA,qBAAA,QACA,wBAAA,KACA,oBAAA,QACA,uBAAA,KAEA,MAAA,KACA,aAAA,QAfF,YAME,cAAA,QACA,sBAAA,QACA,yBAAA,KACA,qBAAA,QACA,wBAAA,KACA,oBAAA,QACA,uBAAA,KAEA,MAAA,KACA,aAAA,QAfF,eAME,cAAA,QACA,sBAAA,QACA,yBAAA,KACA,qBAAA,QACA,wBAAA,KACA,oBAAA,QACA,uBAAA,KAEA,MAAA,KACA,aAAA,QAfF,cAME,cAAA,QACA,sBAAA,QACA,yBAAA,KACA,qBAAA,QACA,wBAAA,KACA,oBAAA,QACA,uBAAA,KAEA,MAAA,KACA,aAAA,QAfF,aAME,cAAA,QACA,sBAAA,QACA,yBAAA,KACA,qBAAA,QACA,wBAAA,KACA,oBAAA,QACA,uBAAA,KAEA,MAAA,KACA,aAAA,QAfF,YAME,cAAA,QACA,sBAAA,QACA,yBAAA,KACA,qBAAA,QACA,wBAAA,KACA,oBAAA,QACA,uBAAA,KAEA,MAAA,KACA,aAAA,QDgIA,kBACE,WAAA,KACA,2BAAA,MHvEF,4BGqEA,qBACE,WAAA,KACA,2BAAA,OHvEF,4BGqEA,qBACE,WAAA,KACA,2BAAA,OHvEF,4BGqEA,qBACE,WAAA,KACA,2BAAA,OHvEF,6BGqEA,qBACE,WAAA,KACA,2BAAA,OHvEF,6BGqEA,sBACE,WAAA,KACA,2BAAA,OE/IN,YACE,cAAA,MASF,gBACE,YAAA,oBACA,eAAA,oBACA,cAAA,EboRI,UAAA,QahRJ,YAAA,IAIF,mBACE,YAAA,kBACA,eAAA,kBb0QI,UAAA,QatQN,mBACE,YAAA,mBACA,eAAA,mBboQI,UAAA,QcjSN,WACE,WAAA,OdgSI,UAAA,Oc5RJ,MAAA,QCLF,cACE,QAAA,MACA,MAAA,KACA,QAAA,QAAA,Of8RI,UAAA,Ke3RJ,YAAA,IACA,YAAA,IACA,MAAA,QACA,iBAAA,KACA,gBAAA,YACA,OAAA,IAAA,MAAA,QACA,mBAAA,KAAA,gBAAA,KAAA,WAAA,KdGE,cAAA,OeHE,WAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YAIA,uCDhBN,cCiBQ,WAAA,MDGN,yBACE,SAAA,OAEA,wDACE,OAAA,QAKJ,oBACE,MAAA,QACA,iBAAA,KACA,aAAA,QACA,QAAA,EAKE,WAAA,EAAA,EAAA,EAAA,OAAA,qBAOJ,2CAEE,OAAA,MAIF,gCACE,MAAA,QAEA,QAAA,EAHF,2BACE,MAAA,QAEA,QAAA,EAQF,uBAAA,wBAEE,iBAAA,QAGA,QAAA,EAIF,oCACE,QAAA,QAAA,OACA,OAAA,SAAA,QACA,mBAAA,OAAA,kBAAA,OACA,MAAA,QE3EF,iBAAA,QF6EE,eAAA,KACA,aAAA,QACA,aAAA,MACA,aAAA,EACA,wBAAA,IACA,cAAA,ECtEE,WAAA,MAAA,KAAA,WAAA,CAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YAIA,uCDuDJ,oCCtDM,WAAA,MDqEN,yEACE,iBAAA,QAGF,0CACE,QAAA,QAAA,OACA,OAAA,SAAA,QACA,mBAAA,OAAA,kBAAA,OACA,MAAA,QE9FF,iBAAA,QFgGE,eAAA,KACA,aAAA,QACA,aAAA,MACA,aAAA,EACA,wBAAA,IACA,cAAA,ECzFE,mBAAA,MAAA,KAAA,WAAA,CAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YAAA,WAAA,MAAA,KAAA,WAAA,CAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YAIA,uCD0EJ,0CCzEM,mBAAA,KAAA,WAAA,MDwFN,+EACE,iBAAA,QASJ,wBACE,QAAA,MACA,MAAA,KACA,QAAA,QAAA,EACA,cAAA,EACA,YAAA,IACA,MAAA,QACA,iBAAA,YACA,OAAA,MAAA,YACA,aAAA,IAAA,EAEA,wCAAA,wCAEE,cAAA,EACA,aAAA,EAWJ,iBACE,WAAA,0BACA,QAAA,OAAA,MfmJI,UAAA,QClRF,cAAA,McmIF,uCACE,QAAA,OAAA,MACA,OAAA,QAAA,OACA,mBAAA,MAAA,kBAAA,MAGF,6CACE,QAAA,OAAA,MACA,OAAA,QAAA,OACA,mBAAA,MAAA,kBAAA,MAIJ,iBACE,WAAA,yBACA,QAAA,MAAA,KfgII,UAAA,QClRF,cAAA,McsJF,uCACE,QAAA,MAAA,KACA,OAAA,OAAA,MACA,mBAAA,KAAA,kBAAA,KAGF,6CACE,QAAA,MAAA,KACA,OAAA,OAAA,MACA,mBAAA,KAAA,kBAAA,KAQF,sBACE,WAAA,2BAGF,yBACE,WAAA,0BAGF,yBACE,WAAA,yBAKJ,oBACE,MAAA,KACA,OAAA,KACA,QAAA,QAEA,mDACE,OAAA,QAGF,uCACE,OAAA,Md/LA,cAAA,OcmMF,0CACE,OAAA,MdpMA,cAAA,OiBdJ,aACE,QAAA,MACA,MAAA,KACA,QAAA,QAAA,QAAA,QAAA,OAEA,mBAAA,oBlB2RI,UAAA,KkBxRJ,YAAA,IACA,YAAA,IACA,MAAA,QACA,iBAAA,KACA,iBAAA,gOACA,kBAAA,UACA,oBAAA,MAAA,OAAA,OACA,gBAAA,KAAA,KACA,OAAA,IAAA,MAAA,QjBFE,cAAA,OeHE,WAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YESJ,mBAAA,KAAA,gBAAA,KAAA,WAAA,KFLI,uCEfN,aFgBQ,WAAA,MEMN,mBACE,aAAA,QACA,QAAA,EAKE,WAAA,EAAA,EAAA,EAAA,OAAA,qBAIJ,uBAAA,mCAEE,cAAA,OACA,iBAAA,KAGF,sBAEE,iBAAA,QAKF,4BACE,MAAA,YACA,YAAA,EAAA,EAAA,EAAA,QAIJ,gBACE,YAAA,OACA,eAAA,OACA,aAAA,MlByOI,UAAA,QkBrON,gBACE,YAAA,MACA,eAAA,MACA,aAAA,KlBkOI,UAAA,QmBjSN,YACE,QAAA,MACA,WAAA,OACA,aAAA,MACA,cAAA,QAEA,8BACE,MAAA,KACA,YAAA,OAIJ,kBACE,MAAA,IACA,OAAA,IACA,WAAA,MACA,eAAA,IACA,iBAAA,KACA,kBAAA,UACA,oBAAA,OACA,gBAAA,QACA,OAAA,IAAA,MAAA,gBACA,mBAAA,KAAA,gBAAA,KAAA,WAAA,KACA,2BAAA,MAAA,aAAA,MAGA,iClBXE,cAAA,MkBeF,8BAEE,cAAA,IAGF,yBACE,OAAA,gBAGF,wBACE,aAAA,QACA,QAAA,EACA,WAAA,EAAA,EAAA,EAAA,OAAA,qBAGF,0BACE,iBAAA,QACA,aAAA,QAEA,yCAII,iBAAA,8NAIJ,sCAII,iBAAA,sIAKN,+CACE,iBAAA,QACA,aAAA,QAKE,iBAAA,wNAIJ,2BACE,eAAA,KACA,OAAA,KACA,QAAA,GAOA,6CAAA,8CACE,QAAA,GAcN,aACE,aAAA,MAEA,+BACE,MAAA,IACA,YAAA,OACA,iBAAA,uJACA,oBAAA,KAAA,OlB9FA,cAAA,IeHE,WAAA,oBAAA,KAAA,YAIA,uCGyFJ,+BHxFM,WAAA,MGgGJ,qCACE,iBAAA,yIAGF,uCACE,oBAAA,MAAA,OAKE,iBAAA,sIAMR,mBACE,QAAA,aACA,aAAA,KAGF,WACE,SAAA,SACA,KAAA,cACA,eAAA,KAIE,yBAAA,0BACE,eAAA,KACA,OAAA,KACA,QAAA,IC9IN,YACE,MAAA,KACA,OAAA,OACA,QAAA,EACA,iBAAA,YACA,mBAAA,KAAA,gBAAA,KAAA,WAAA,KAEA,kBACE,QAAA,EAIA,wCAA0B,WAAA,EAAA,EAAA,EAAA,IAAA,IAAA,CAAA,EAAA,EAAA,EAAA,OAAA,qBAC1B,oCAA0B,WAAA,EAAA,EAAA,EAAA,IAAA,IAAA,CAAA,EAAA,EAAA,EAAA,OAAA,qBAG5B,8BACE,OAAA,EAGF,kCACE,MAAA,KACA,OAAA,KACA,WAAA,QHzBF,iBAAA,QG2BE,OAAA,EnBZA,cAAA,KeHE,mBAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YAAA,WAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YImBF,mBAAA,KAAA,WAAA,KJfE,uCIMJ,kCJLM,mBAAA,KAAA,WAAA,MIgBJ,yCHjCF,iBAAA,QGsCA,2CACE,MAAA,KACA,OAAA,MACA,MAAA,YACA,OAAA,QACA,iBAAA,QACA,aAAA,YnB7BA,cAAA,KmBkCF,8BACE,MAAA,KACA,OAAA,KHnDF,iBAAA,QGqDE,OAAA,EnBtCA,cAAA,KeHE,gBAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YAAA,WAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YI6CF,gBAAA,KAAA,WAAA,KJzCE,uCIiCJ,8BJhCM,gBAAA,KAAA,WAAA,MI0CJ,qCH3DF,iBAAA,QGgEA,8BACE,MAAA,KACA,OAAA,MACA,MAAA,YACA,OAAA,QACA,iBAAA,QACA,aAAA,YnBvDA,cAAA,KmB4DF,qBACE,eAAA,KAEA,2CACE,iBAAA,QAGF,uCACE,iBAAA,QCvFN,eACE,SAAA,SAEA,6BtB+iFF,4BsB7iFI,OAAA,mBACA,YAAA,KAGF,qBACE,SAAA,SACA,IAAA,EACA,KAAA,EACA,OAAA,KACA,QAAA,KAAA,OACA,eAAA,KACA,OAAA,IAAA,MAAA,YACA,iBAAA,EAAA,ELDE,WAAA,QAAA,IAAA,WAAA,CAAA,UAAA,IAAA,YAIA,uCKXJ,qBLYM,WAAA,MKCN,6BACE,QAAA,KAAA,OAEA,+CACE,MAAA,YADF,0CACE,MAAA,YAGF,0DAEE,YAAA,SACA,eAAA,QAHF,mCAAA,qDAEE,YAAA,SACA,eAAA,QAGF,8CACE,YAAA,SACA,eAAA,QAIJ,4BACE,YAAA,SACA,eAAA,QAMA,gEACE,QAAA,IACA,UAAA,WAAA,mBAAA,mBAFF,yCtBmjFJ,2DACA,kCsBnjFM,QAAA,IACA,UAAA,WAAA,mBAAA,mBAKF,oDACE,QAAA,IACA,UAAA,WAAA,mBAAA,mBCtDN,aACE,SAAA,SACA,QAAA,KACA,UAAA,KACA,YAAA,QACA,MAAA,KAEA,2BvB2mFF,0BuBzmFI,SAAA,SACA,KAAA,EAAA,EAAA,KACA,MAAA,GACA,UAAA,EAIF,iCvBymFF,gCuBvmFI,QAAA,EAMF,kBACE,SAAA,SACA,QAAA,EAEA,wBACE,QAAA,EAWN,kBACE,QAAA,KACA,YAAA,OACA,QAAA,QAAA,OtBsPI,UAAA,KsBpPJ,YAAA,IACA,YAAA,IACA,MAAA,QACA,WAAA,OACA,YAAA,OACA,iBAAA,QACA,OAAA,IAAA,MAAA,QrBpCE,cAAA,OFuoFJ,qBuBzlFA,8BvBulFA,6BACA,kCuBplFE,QAAA,MAAA,KtBgOI,UAAA,QClRF,cAAA,MFgpFJ,qBuBzlFA,8BvBulFA,6BACA,kCuBplFE,QAAA,OAAA,MtBuNI,UAAA,QClRF,cAAA,MqBgEJ,6BvBulFA,6BuBrlFE,cAAA,KvB0lFF,uEuB7kFI,8FrB/DA,wBAAA,EACA,2BAAA,EFgpFJ,iEuB3kFI,2FrBtEA,wBAAA,EACA,2BAAA,EqBgFF,0IACE,YAAA,KrBpEA,uBAAA,EACA,0BAAA,EsBzBF,gBACE,QAAA,KACA,MAAA,KACA,WAAA,OvByQE,UAAA,OuBtQF,MAAA,QAGF,eACE,SAAA,SACA,IAAA,KACA,QAAA,EACA,QAAA,KACA,UAAA,KACA,QAAA,OAAA,MACA,WAAA,MvB4PE,UAAA,QuBzPF,MAAA,KACA,iBAAA,mBtB1BA,cAAA,OFmsFJ,0BACA,yBwBrqFI,sCxBmqFJ,qCwBjqFM,QAAA,MA9CF,uBAAA,mCAoDE,aAAA,QAGE,cAAA,qBACA,iBAAA,2OACA,kBAAA,UACA,oBAAA,MAAA,wBAAA,OACA,gBAAA,sBAAA,sBAGF,6BAAA,yCACE,aAAA,QACA,WAAA,EAAA,EAAA,EAAA,OAAA,oBAhEJ,2CAAA,+BAyEI,cAAA,qBACA,oBAAA,IAAA,wBAAA,MAAA,wBA1EJ,sBAAA,kCAiFE,aAAA,QAGE,kDAAA,gDAAA,8DAAA,4DAEE,cAAA,SACA,iBAAA,+NAAA,CAAA,2OACA,oBAAA,MAAA,OAAA,MAAA,CAAA,OAAA,MAAA,QACA,gBAAA,KAAA,IAAA,CAAA,sBAAA,sBAIJ,4BAAA,wCACE,aAAA,QACA,WAAA,EAAA,EAAA,EAAA,OAAA,oBA/FJ,2BAAA,uCAsGE,aAAA,QAEA,mCAAA,+CACE,iBAAA,QAGF,iCAAA,6CACE,WAAA,EAAA,EAAA,EAAA,OAAA,oBAGF,6CAAA,yDACE,MAAA,QAKJ,qDACE,YAAA,KAvHF,oCxBwwFJ,mCwBxwFI,gDxBuwFJ,+CwBxoFQ,QAAA,EAIF,0CxB0oFN,yCwB1oFM,sDxByoFN,qDwBxoFQ,QAAA,EAjHN,kBACE,QAAA,KACA,MAAA,KACA,WAAA,OvByQE,UAAA,OuBtQF,MAAA,QAGF,iBACE,SAAA,SACA,IAAA,KACA,QAAA,EACA,QAAA,KACA,UAAA,KACA,QAAA,OAAA,MACA,WAAA,MvB4PE,UAAA,QuBzPF,MAAA,KACA,iBAAA,mBtB1BA,cAAA,OF4xFJ,8BACA,6BwB9vFI,0CxB4vFJ,yCwB1vFM,QAAA,MA9CF,yBAAA,qCAoDE,aAAA,QAGE,cAAA,qBACA,iBAAA,2TACA,kBAAA,UACA,oBAAA,MAAA,wBAAA,OACA,gBAAA,sBAAA,sBAGF,+BAAA,2CACE,aAAA,QACA,WAAA,EAAA,EAAA,EAAA,OAAA,oBAhEJ,6CAAA,iCAyEI,cAAA,qBACA,oBAAA,IAAA,wBAAA,MAAA,wBA1EJ,wBAAA,oCAiFE,aAAA,QAGE,oDAAA,kDAAA,gEAAA,8DAEE,cAAA,SACA,iBAAA,+NAAA,CAAA,2TACA,oBAAA,MAAA,OAAA,MAAA,CAAA,OAAA,MAAA,QACA,gBAAA,KAAA,IAAA,CAAA,sBAAA,sBAIJ,8BAAA,0CACE,aAAA,QACA,WAAA,EAAA,EAAA,EAAA,OAAA,oBA/FJ,6BAAA,yCAsGE,aAAA,QAEA,qCAAA,iDACE,iBAAA,QAGF,mCAAA,+CACE,WAAA,EAAA,EAAA,EAAA,OAAA,oBAGF,+CAAA,2DACE,MAAA,QAKJ,uDACE,YAAA,KAvHF,sCxBi2FJ,qCwBj2FI,kDxBg2FJ,iDwB/tFQ,QAAA,EAEF,4CxBmuFN,2CwBnuFM,wDxBkuFN,uDwBjuFQ,QAAA,ECtIR,KACE,QAAA,aAEA,YAAA,IACA,YAAA,IACA,MAAA,QACA,WAAA,OACA,gBAAA,KAEA,eAAA,OACA,OAAA,QACA,oBAAA,KAAA,iBAAA,KAAA,YAAA,KACA,iBAAA,YACA,OAAA,IAAA,MAAA,YC8GA,QAAA,QAAA,OzBsKI,UAAA,KClRF,cAAA,OeHE,WAAA,MAAA,KAAA,WAAA,CAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YAIA,uCQhBN,KRiBQ,WAAA,MQAN,WACE,MAAA,QAIF,sBAAA,WAEE,QAAA,EACA,WAAA,EAAA,EAAA,EAAA,OAAA,qBAcF,cAAA,cAAA,uBAGE,eAAA,KACA,QAAA,IAYF,aCvCA,MAAA,KRhBA,iBAAA,QQkBA,aAAA,QAGA,mBACE,MAAA,KRtBF,iBAAA,QQwBE,aAAA,QAGF,8BAAA,mBAEE,MAAA,KR7BF,iBAAA,QQ+BE,aAAA,QAKE,WAAA,EAAA,EAAA,EAAA,OAAA,oBAIJ,+BAAA,gCAAA,oBAAA,oBAAA,mCAKE,MAAA,KACA,iBAAA,QAGA,aAAA,QAEA,qCAAA,sCAAA,0BAAA,0BAAA,yCAKI,WAAA,EAAA,EAAA,EAAA,OAAA,oBAKN,sBAAA,sBAEE,MAAA,KACA,iBAAA,QAGA,aAAA,QDZF,eCvCA,MAAA,KRhBA,iBAAA,QQkBA,aAAA,QAGA,qBACE,MAAA,KRtBF,iBAAA,QQwBE,aAAA,QAGF,gCAAA,qBAEE,MAAA,KR7BF,iBAAA,QQ+BE,aAAA,QAKE,WAAA,EAAA,EAAA,EAAA,OAAA,qBAIJ,iCAAA,kCAAA,sBAAA,sBAAA,qCAKE,MAAA,KACA,iBAAA,QAGA,aAAA,QAEA,uCAAA,wCAAA,4BAAA,4BAAA,2CAKI,WAAA,EAAA,EAAA,EAAA,OAAA,qBAKN,wBAAA,wBAEE,MAAA,KACA,iBAAA,QAGA,aAAA,QDZF,aCvCA,MAAA,KRhBA,iBAAA,QQkBA,aAAA,QAGA,mBACE,MAAA,KRtBF,iBAAA,QQwBE,aAAA,QAGF,8BAAA,mBAEE,MAAA,KR7BF,iBAAA,QQ+BE,aAAA,QAKE,WAAA,EAAA,EAAA,EAAA,OAAA,oBAIJ,+BAAA,gCAAA,oBAAA,oBAAA,mCAKE,MAAA,KACA,iBAAA,QAGA,aAAA,QAEA,qCAAA,sCAAA,0BAAA,0BAAA,yCAKI,WAAA,EAAA,EAAA,EAAA,OAAA,oBAKN,sBAAA,sBAEE,MAAA,KACA,iBAAA,QAGA,aAAA,QDZF,UCvCA,MAAA,KRhBA,iBAAA,QQkBA,aAAA,QAGA,gBACE,MAAA,KRtBF,iBAAA,QQwBE,aAAA,QAGF,2BAAA,gBAEE,MAAA,KR7BF,iBAAA,QQ+BE,aAAA,QAKE,WAAA,EAAA,EAAA,EAAA,OAAA,oBAIJ,4BAAA,6BAAA,iBAAA,iBAAA,gCAKE,MAAA,KACA,iBAAA,QAGA,aAAA,QAEA,kCAAA,mCAAA,uBAAA,uBAAA,sCAKI,WAAA,EAAA,EAAA,EAAA,OAAA,oBAKN,mBAAA,mBAEE,MAAA,KACA,iBAAA,QAGA,aAAA,QDZF,aCvCA,MAAA,KRhBA,iBAAA,QQkBA,aAAA,QAGA,mBACE,MAAA,KRtBF,iBAAA,QQwBE,aAAA,QAGF,8BAAA,mBAEE,MAAA,KR7BF,iBAAA,QQ+BE,aAAA,QAKE,WAAA,EAAA,EAAA,EAAA,OAAA,mBAIJ,+BAAA,gCAAA,oBAAA,oBAAA,mCAKE,MAAA,KACA,iBAAA,QAGA,aAAA,QAEA,qCAAA,sCAAA,0BAAA,0BAAA,yCAKI,WAAA,EAAA,EAAA,EAAA,OAAA,mBAKN,sBAAA,sBAEE,MAAA,KACA,iBAAA,QAGA,aAAA,QDZF,YCvCA,MAAA,KRhBA,iBAAA,QQkBA,aAAA,QAGA,kBACE,MAAA,KRtBF,iBAAA,QQwBE,aAAA,QAGF,6BAAA,kBAEE,MAAA,KR7BF,iBAAA,QQ+BE,aAAA,QAKE,WAAA,EAAA,EAAA,EAAA,OAAA,mBAIJ,8BAAA,+BAAA,mBAAA,mBAAA,kCAKE,MAAA,KACA,iBAAA,QAGA,aAAA,QAEA,oCAAA,qCAAA,yBAAA,yBAAA,wCAKI,WAAA,EAAA,EAAA,EAAA,OAAA,mBAKN,qBAAA,qBAEE,MAAA,KACA,iBAAA,QAGA,aAAA,QDZF,WCvCA,MAAA,KRhBA,iBAAA,QQkBA,aAAA,QAGA,iBACE,MAAA,KRtBF,iBAAA,QQwBE,aAAA,QAGF,4BAAA,iBAEE,MAAA,KR7BF,iBAAA,QQ+BE,aAAA,QAKE,WAAA,EAAA,EAAA,EAAA,OAAA,qBAIJ,6BAAA,8BAAA,kBAAA,kBAAA,iCAKE,MAAA,KACA,iBAAA,QAGA,aAAA,QAEA,mCAAA,oCAAA,wBAAA,wBAAA,uCAKI,WAAA,EAAA,EAAA,EAAA,OAAA,qBAKN,oBAAA,oBAEE,MAAA,KACA,iBAAA,QAGA,aAAA,QDZF,UCvCA,MAAA,KRhBA,iBAAA,QQkBA,aAAA,QAGA,gBACE,MAAA,KRtBF,iBAAA,QQwBE,aAAA,QAGF,2BAAA,gBAEE,MAAA,KR7BF,iBAAA,QQ+BE,aAAA,QAKE,WAAA,EAAA,EAAA,EAAA,OAAA,kBAIJ,4BAAA,6BAAA,iBAAA,iBAAA,gCAKE,MAAA,KACA,iBAAA,QAGA,aAAA,QAEA,kCAAA,mCAAA,uBAAA,uBAAA,sCAKI,WAAA,EAAA,EAAA,EAAA,OAAA,kBAKN,mBAAA,mBAEE,MAAA,KACA,iBAAA,QAGA,aAAA,QDNF,qBCmBA,MAAA,QACA,aAAA,QAEA,2BACE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,sCAAA,2BAEE,WAAA,EAAA,EAAA,EAAA,OAAA,oBAGF,uCAAA,wCAAA,4BAAA,0CAAA,4BAKE,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,6CAAA,8CAAA,kCAAA,gDAAA,kCAKI,WAAA,EAAA,EAAA,EAAA,OAAA,oBAKN,8BAAA,8BAEE,MAAA,QACA,iBAAA,YDvDF,uBCmBA,MAAA,QACA,aAAA,QAEA,6BACE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,wCAAA,6BAEE,WAAA,EAAA,EAAA,EAAA,OAAA,qBAGF,yCAAA,0CAAA,8BAAA,4CAAA,8BAKE,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,+CAAA,gDAAA,oCAAA,kDAAA,oCAKI,WAAA,EAAA,EAAA,EAAA,OAAA,qBAKN,gCAAA,gCAEE,MAAA,QACA,iBAAA,YDvDF,qBCmBA,MAAA,QACA,aAAA,QAEA,2BACE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,sCAAA,2BAEE,WAAA,EAAA,EAAA,EAAA,OAAA,mBAGF,uCAAA,wCAAA,4BAAA,0CAAA,4BAKE,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,6CAAA,8CAAA,kCAAA,gDAAA,kCAKI,WAAA,EAAA,EAAA,EAAA,OAAA,mBAKN,8BAAA,8BAEE,MAAA,QACA,iBAAA,YDvDF,kBCmBA,MAAA,QACA,aAAA,QAEA,wBACE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,mCAAA,wBAEE,WAAA,EAAA,EAAA,EAAA,OAAA,oBAGF,oCAAA,qCAAA,yBAAA,uCAAA,yBAKE,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,0CAAA,2CAAA,+BAAA,6CAAA,+BAKI,WAAA,EAAA,EAAA,EAAA,OAAA,oBAKN,2BAAA,2BAEE,MAAA,QACA,iBAAA,YDvDF,qBCmBA,MAAA,QACA,aAAA,QAEA,2BACE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,sCAAA,2BAEE,WAAA,EAAA,EAAA,EAAA,OAAA,mBAGF,uCAAA,wCAAA,4BAAA,0CAAA,4BAKE,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,6CAAA,8CAAA,kCAAA,gDAAA,kCAKI,WAAA,EAAA,EAAA,EAAA,OAAA,mBAKN,8BAAA,8BAEE,MAAA,QACA,iBAAA,YDvDF,oBCmBA,MAAA,QACA,aAAA,QAEA,0BACE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,qCAAA,0BAEE,WAAA,EAAA,EAAA,EAAA,OAAA,mBAGF,sCAAA,uCAAA,2BAAA,yCAAA,2BAKE,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,4CAAA,6CAAA,iCAAA,+CAAA,iCAKI,WAAA,EAAA,EAAA,EAAA,OAAA,mBAKN,6BAAA,6BAEE,MAAA,QACA,iBAAA,YDvDF,mBCmBA,MAAA,QACA,aAAA,QAEA,yBACE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,oCAAA,yBAEE,WAAA,EAAA,EAAA,EAAA,OAAA,qBAGF,qCAAA,sCAAA,0BAAA,wCAAA,0BAKE,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,2CAAA,4CAAA,gCAAA,8CAAA,gCAKI,WAAA,EAAA,EAAA,EAAA,OAAA,qBAKN,4BAAA,4BAEE,MAAA,QACA,iBAAA,YDvDF,kBCmBA,MAAA,QACA,aAAA,QAEA,wBACE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,mCAAA,wBAEE,WAAA,EAAA,EAAA,EAAA,OAAA,kBAGF,oCAAA,qCAAA,yBAAA,uCAAA,yBAKE,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,0CAAA,2CAAA,+BAAA,6CAAA,+BAKI,WAAA,EAAA,EAAA,EAAA,OAAA,kBAKN,2BAAA,2BAEE,MAAA,QACA,iBAAA,YD3CJ,UACE,YAAA,IACA,MAAA,QACA,gBAAA,UAEA,gBACE,MAAA,QAQF,mBAAA,mBAEE,MAAA,QAWJ,mBAAA,QCuBE,QAAA,MAAA,KzBsKI,UAAA,QClRF,cAAA,MuByFJ,mBAAA,QCmBE,QAAA,OAAA,MzBsKI,UAAA,QClRF,cAAA,MyBnBJ,MVgBM,WAAA,QAAA,KAAA,OAIA,uCUpBN,MVqBQ,WAAA,MUlBN,iBACE,QAAA,EAMF,qBACE,QAAA,KAIJ,YACE,OAAA,EACA,SAAA,OVDI,WAAA,OAAA,KAAA,KAIA,uCULN,YVMQ,WAAA,MUDN,gCACE,MAAA,EACA,OAAA,KVNE,WAAA,MAAA,KAAA,KAIA,uCUAJ,gCVCM,WAAA,MjBs3GR,UADA,SAEA,W4B34GA,QAIE,SAAA,SAGF,iBACE,YAAA,OCqBE,wBACE,QAAA,aACA,YAAA,OACA,eAAA,OACA,QAAA,GAhCJ,WAAA,KAAA,MACA,aAAA,KAAA,MAAA,YACA,cAAA,EACA,YAAA,KAAA,MAAA,YAqDE,8BACE,YAAA,ED3CN,eACE,SAAA,SACA,QAAA,KACA,QAAA,KACA,UAAA,MACA,QAAA,MAAA,EACA,OAAA,E3B+QI,UAAA,K2B7QJ,MAAA,QACA,WAAA,KACA,WAAA,KACA,iBAAA,KACA,gBAAA,YACA,OAAA,IAAA,MAAA,gB1BVE,cAAA,O0BcF,+BACE,IAAA,KACA,KAAA,EACA,WAAA,QAYA,qBACE,cAAA,MAEA,qCACE,MAAA,KACA,KAAA,EAIJ,mBACE,cAAA,IAEA,mCACE,MAAA,EACA,KAAA,KnBCJ,yBmBfA,wBACE,cAAA,MAEA,wCACE,MAAA,KACA,KAAA,EAIJ,sBACE,cAAA,IAEA,sCACE,MAAA,EACA,KAAA,MnBCJ,yBmBfA,wBACE,cAAA,MAEA,wCACE,MAAA,KACA,KAAA,EAIJ,sBACE,cAAA,IAEA,sCACE,MAAA,EACA,KAAA,MnBCJ,yBmBfA,wBACE,cAAA,MAEA,wCACE,MAAA,KACA,KAAA,EAIJ,sBACE,cAAA,IAEA,sCACE,MAAA,EACA,KAAA,MnBCJ,0BmBfA,wBACE,cAAA,MAEA,wCACE,MAAA,KACA,KAAA,EAIJ,sBACE,cAAA,IAEA,sCACE,MAAA,EACA,KAAA,MnBCJ,0BmBfA,yBACE,cAAA,MAEA,yCACE,MAAA,KACA,KAAA,EAIJ,uBACE,cAAA,IAEA,uCACE,MAAA,EACA,KAAA,MAUN,uCACE,IAAA,KACA,OAAA,KACA,WAAA,EACA,cAAA,QC9CA,gCACE,QAAA,aACA,YAAA,OACA,eAAA,OACA,QAAA,GAzBJ,WAAA,EACA,aAAA,KAAA,MAAA,YACA,cAAA,KAAA,MACA,YAAA,KAAA,MAAA,YA8CE,sCACE,YAAA,ED0BJ,wCACE,IAAA,EACA,MAAA,KACA,KAAA,KACA,WAAA,EACA,YAAA,QC5DA,iCACE,QAAA,aACA,YAAA,OACA,eAAA,OACA,QAAA,GAlBJ,WAAA,KAAA,MAAA,YACA,aAAA,EACA,cAAA,KAAA,MAAA,YACA,YAAA,KAAA,MAuCE,uCACE,YAAA,EDoCF,iCACE,eAAA,EAMJ,0CACE,IAAA,EACA,MAAA,KACA,KAAA,KACA,WAAA,EACA,aAAA,QC7EA,mCACE,QAAA,aACA,YAAA,OACA,eAAA,OACA,QAAA,GAWA,mCACE,QAAA,KAGF,oCACE,QAAA,aACA,aAAA,OACA,eAAA,OACA,QAAA,GA9BN,WAAA,KAAA,MAAA,YACA,aAAA,KAAA,MACA,cAAA,KAAA,MAAA,YAiCE,yCACE,YAAA,EDqDF,oCACE,eAAA,EAON,kBACE,OAAA,EACA,OAAA,MAAA,EACA,SAAA,OACA,WAAA,IAAA,MAAA,gBAMF,eACE,QAAA,MACA,MAAA,KACA,QAAA,OAAA,KACA,MAAA,KACA,YAAA,IACA,MAAA,QACA,WAAA,QACA,gBAAA,KACA,YAAA,OACA,iBAAA,YACA,OAAA,EAcA,qBAAA,qBAEE,MAAA,QVzJF,iBAAA,QU8JA,sBAAA,sBAEE,MAAA,KACA,gBAAA,KVjKF,iBAAA,QUqKA,wBAAA,wBAEE,MAAA,QACA,eAAA,KACA,iBAAA,YAMJ,oBACE,QAAA,MAIF,iBACE,QAAA,MACA,QAAA,MAAA,KACA,cAAA,E3B0GI,UAAA,Q2BxGJ,MAAA,QACA,YAAA,OAIF,oBACE,QAAA,MACA,QAAA,OAAA,KACA,MAAA,QAIF,oBACE,MAAA,QACA,iBAAA,QACA,aAAA,gBAGA,mCACE,MAAA,QAEA,yCAAA,yCAEE,MAAA,KVhNJ,iBAAA,sBUoNE,0CAAA,0CAEE,MAAA,KVtNJ,iBAAA,QU0NE,4CAAA,4CAEE,MAAA,QAIJ,sCACE,aAAA,gBAGF,wCACE,MAAA,QAGF,qCACE,MAAA,QE5OJ,W9B2rHA,oB8BzrHE,SAAA,SACA,QAAA,YACA,eAAA,O9B6rHF,yB8B3rHE,gBACE,SAAA,SACA,KAAA,EAAA,EAAA,K9BmsHJ,4CACA,0CAIA,gCADA,gCADA,+BADA,+B8BhsHE,mC9ByrHF,iCAIA,uBADA,uBADA,sBADA,sB8BprHI,QAAA,EAKJ,aACE,QAAA,KACA,UAAA,KACA,gBAAA,WAEA,0BACE,MAAA,K9BgsHJ,wC8B1rHE,kCAEE,YAAA,K9B4rHJ,4C8BxrHE,uD5BRE,wBAAA,EACA,2BAAA,EFqsHJ,6C8BrrHE,+B9BorHF,iCEvrHI,uBAAA,EACA,0BAAA,E4BqBJ,uBACE,cAAA,SACA,aAAA,SAEA,8BAAA,uCAAA,sCAGE,YAAA,EAGF,0CACE,aAAA,EAIJ,0CAAA,+BACE,cAAA,QACA,aAAA,QAGF,0CAAA,+BACE,cAAA,OACA,aAAA,OAoBF,oBACE,eAAA,OACA,YAAA,WACA,gBAAA,OAEA,yB9BmpHF,+B8BjpHI,MAAA,K9BqpHJ,iD8BlpHE,2CAEE,WAAA,K9BopHJ,qD8BhpHE,gE5BvFE,2BAAA,EACA,0BAAA,EF2uHJ,sD8BhpHE,8B5B1GE,uBAAA,EACA,wBAAA,E6BxBJ,KACE,QAAA,KACA,UAAA,KACA,aAAA,EACA,cAAA,EACA,WAAA,KAGF,UACE,QAAA,MACA,QAAA,MAAA,KAGA,MAAA,QACA,gBAAA,KdHI,WAAA,MAAA,KAAA,WAAA,CAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,YAIA,uCcPN,UdQQ,WAAA,McCN,gBAAA,gBAEE,MAAA,QAKF,mBACE,MAAA,QACA,eAAA,KACA,OAAA,QAQJ,UACE,cAAA,IAAA,MAAA,QAEA,oBACE,cAAA,KACA,WAAA,IACA,OAAA,IAAA,MAAA,Y7BlBA,uBAAA,OACA,wBAAA,O6BoBA,0BAAA,0BAEE,aAAA,QAAA,QAAA,QAEA,UAAA,QAGF,6BACE,MAAA,QACA,iBAAA,YACA,aAAA,Y/BixHN,mC+B7wHE,2BAEE,MAAA,QACA,iBAAA,KACA,aAAA,QAAA,QAAA,KAGF,yBAEE,WAAA,K7B5CA,uBAAA,EACA,wBAAA,E6BuDF,qBACE,WAAA,IACA,OAAA,E7BnEA,cAAA,O6BuEF,4B/BmwHF,2B+BjwHI,MAAA,KbxFF,iBAAA,QlB+1HF,oB+B5vHE,oBAEE,KAAA,EAAA,EAAA,KACA,WAAA,O/B+vHJ,yB+B1vHE,yBAEE,WAAA,EACA,UAAA,EACA,WAAA,OAMF,8B/BuvHF,mC+BtvHI,MAAA,KAUF,uBACE,QAAA,KAEF,qBACE,QAAA,MCxHJ,QACE,SAAA,SACA,QAAA,KACA,UAAA,KACA,YAAA,OACA,gBAAA,cACA,YAAA,MAEA,eAAA,MAOA,mBhCs2HF,yBAGA,sBADA,sBADA,sBAGA,sBACA,uBgC12HI,QAAA,KACA,UAAA,QACA,YAAA,OACA,gBAAA,cAoBJ,cACE,YAAA,SACA,eAAA,SACA,aAAA,K/B2OI,UAAA,Q+BzOJ,gBAAA,KACA,YAAA,OAaF,YACE,QAAA,KACA,eAAA,OACA,aAAA,EACA,cAAA,EACA,WAAA,KAEA,sBACE,cAAA,EACA,aAAA,EAGF,2BACE,SAAA,OASJ,aACE,YAAA,MACA,eAAA,MAYF,iBACE,WAAA,KACA,UAAA,EAGA,YAAA,OAIF,gBACE,QAAA,OAAA,O/B6KI,UAAA,Q+B3KJ,YAAA,EACA,iBAAA,YACA,OAAA,IAAA,MAAA,Y9BzGE,cAAA,OeHE,WAAA,WAAA,KAAA,YAIA,uCemGN,gBflGQ,WAAA,Me2GN,sBACE,gBAAA,KAGF,sBACE,gBAAA,KACA,QAAA,EACA,WAAA,EAAA,EAAA,EAAA,OAMJ,qBACE,QAAA,aACA,MAAA,MACA,OAAA,MACA,eAAA,OACA,kBAAA,UACA,oBAAA,OACA,gBAAA,KAGF,mBACE,WAAA,6BACA,WAAA,KvB1FE,yBuBsGA,kBAEI,UAAA,OACA,gBAAA,WAEA,8BACE,eAAA,IAEA,6CACE,SAAA,SAGF,wCACE,cAAA,MACA,aAAA,MAIJ,qCACE,SAAA,QAGF,mCACE,QAAA,eACA,WAAA,KAGF,kCACE,QAAA,KAGF,oCACE,QAAA,KAGF,6BACE,SAAA,QACA,OAAA,EACA,QAAA,KACA,UAAA,EACA,WAAA,kBACA,iBAAA,YACA,aAAA,EACA,YAAA,EfhMJ,WAAA,KekMI,UAAA,KhC+yHV,oCgC7yHQ,iCAEE,OAAA,KACA,WAAA,EACA,cAAA,EAGF,kCACE,QAAA,KACA,UAAA,EACA,QAAA,EACA,WAAA,SvBhKN,yBuBsGA,kBAEI,UAAA,OACA,gBAAA,WAEA,8BACE,eAAA,IAEA,6CACE,SAAA,SAGF,wCACE,cAAA,MACA,aAAA,MAIJ,qCACE,SAAA,QAGF,mCACE,QAAA,eACA,WAAA,KAGF,kCACE,QAAA,KAGF,oCACE,QAAA,KAGF,6BACE,SAAA,QACA,OAAA,EACA,QAAA,KACA,UAAA,EACA,WAAA,kBACA,iBAAA,YACA,aAAA,EACA,YAAA,EfhMJ,WAAA,KekMI,UAAA,KhCo2HV,oCgCl2HQ,iCAEE,OAAA,KACA,WAAA,EACA,cAAA,EAGF,kCACE,QAAA,KACA,UAAA,EACA,QAAA,EACA,WAAA,SvBhKN,yBuBsGA,kBAEI,UAAA,OACA,gBAAA,WAEA,8BACE,eAAA,IAEA,6CACE,SAAA,SAGF,wCACE,cAAA,MACA,aAAA,MAIJ,qCACE,SAAA,QAGF,mCACE,QAAA,eACA,WAAA,KAGF,kCACE,QAAA,KAGF,oCACE,QAAA,KAGF,6BACE,SAAA,QACA,OAAA,EACA,QAAA,KACA,UAAA,EACA,WAAA,kBACA,iBAAA,YACA,aAAA,EACA,YAAA,EfhMJ,WAAA,KekMI,UAAA,KhCy5HV,oCgCv5HQ,iCAEE,OAAA,KACA,WAAA,EACA,cAAA,EAGF,kCACE,QAAA,KACA,UAAA,EACA,QAAA,EACA,WAAA,SvBhKN,0BuBsGA,kBAEI,UAAA,OACA,gBAAA,WAEA,8BACE,eAAA,IAEA,6CACE,SAAA,SAGF,wCACE,cAAA,MACA,aAAA,MAIJ,qCACE,SAAA,QAGF,mCACE,QAAA,eACA,WAAA,KAGF,kCACE,QAAA,KAGF,oCACE,QAAA,KAGF,6BACE,SAAA,QACA,OAAA,EACA,QAAA,KACA,UAAA,EACA,WAAA,kBACA,iBAAA,YACA,aAAA,EACA,YAAA,EfhMJ,WAAA,KekMI,UAAA,KhC88HV,oCgC58HQ,iCAEE,OAAA,KACA,WAAA,EACA,cAAA,EAGF,kCACE,QAAA,KACA,UAAA,EACA,QAAA,EACA,WAAA,SvBhKN,0BuBsGA,mBAEI,UAAA,OACA,gBAAA,WAEA,+BACE,eAAA,IAEA,8CACE,SAAA,SAGF,yCACE,cAAA,MACA,aAAA,MAIJ,sCACE,SAAA,QAGF,oCACE,QAAA,eACA,WAAA,KAGF,mCACE,QAAA,KAGF,qCACE,QAAA,KAGF,8BACE,SAAA,QACA,OAAA,EACA,QAAA,KACA,UAAA,EACA,WAAA,kBACA,iBAAA,YACA,aAAA,EACA,YAAA,EfhMJ,WAAA,KekMI,UAAA,KhCmgIV,qCgCjgIQ,kCAEE,OAAA,KACA,WAAA,EACA,cAAA,EAGF,mCACE,QAAA,KACA,UAAA,EACA,QAAA,EACA,WAAA,SA1DN,eAEI,UAAA,OACA,gBAAA,WAEA,2BACE,eAAA,IAEA,0CACE,SAAA,SAGF,qCACE,cAAA,MACA,aAAA,MAIJ,kCACE,SAAA,QAGF,gCACE,QAAA,eACA,WAAA,KAGF,+BACE,QAAA,KAGF,iCACE,QAAA,KAGF,0BACE,SAAA,QACA,OAAA,EACA,QAAA,KACA,UAAA,EACA,WAAA,kBACA,iBAAA,YACA,aAAA,EACA,YAAA,EfhMJ,WAAA,KekMI,UAAA,KhCujIV,iCgCrjIQ,8BAEE,OAAA,KACA,WAAA,EACA,cAAA,EAGF,+BACE,QAAA,KACA,UAAA,EACA,QAAA,EACA,WAAA,QAcR,4BACE,MAAA,eAEA,kCAAA,kCAEE,MAAA,eAKF,oCACE,MAAA,gBAEA,0CAAA,0CAEE,MAAA,eAGF,6CACE,MAAA,ehCqiIR,2CgCjiII,0CAEE,MAAA,eAIJ,8BACE,MAAA,gBACA,aAAA,eAGF,mCACE,iBAAA,4OAGF,2BACE,MAAA,gBAEA,6BhC8hIJ,mCADA,mCgC1hIM,MAAA,eAOJ,2BACE,MAAA,KAEA,iCAAA,iCAEE,MAAA,KAKF,mCACE,MAAA,sBAEA,yCAAA,yCAEE,MAAA,sBAGF,4CACE,MAAA,sBhCqhIR,0CgCjhII,yCAEE,MAAA,KAIJ,6BACE,MAAA,sBACA,aAAA,qBAGF,kCACE,iBAAA,kPAGF,0BACE,MAAA,sBACA,4BhC+gIJ,kCADA,kCgC3gIM,MAAA,KCvUN,MACE,SAAA,SACA,QAAA,KACA,eAAA,OACA,UAAA,EAEA,UAAA,WACA,iBAAA,KACA,gBAAA,WACA,OAAA,IAAA,MAAA,iB/BME,cAAA,O+BFF,SACE,aAAA,EACA,YAAA,EAGF,kBACE,WAAA,QACA,cAAA,QAEA,8BACE,iBAAA,E/BCF,uBAAA,mBACA,wBAAA,mB+BEA,6BACE,oBAAA,E/BUF,2BAAA,mBACA,0BAAA,mB+BJF,+BjCk1IF,+BiCh1II,WAAA,EAIJ,WAGE,KAAA,EAAA,EAAA,KACA,QAAA,KAAA,KAIF,YACE,cAAA,MAGF,eACE,WAAA,QACA,cAAA,EAGF,sBACE,cAAA,EAQA,sBACE,YAAA,KAQJ,aACE,QAAA,MAAA,KACA,cAAA,EAEA,iBAAA,gBACA,cAAA,IAAA,MAAA,iBAEA,yB/BpEE,cAAA,mBAAA,mBAAA,EAAA,E+ByEJ,aACE,QAAA,MAAA,KAEA,iBAAA,gBACA,WAAA,IAAA,MAAA,iBAEA,wB/B/EE,cAAA,EAAA,EAAA,mBAAA,mB+ByFJ,kBACE,aAAA,OACA,cAAA,OACA,YAAA,OACA,cAAA,EAUF,mBACE,aAAA,OACA,YAAA,OAIF,kBACE,SAAA,SACA,IAAA,EACA,MAAA,EACA,OAAA,EACA,KAAA,EACA,QAAA,K/BnHE,cAAA,mB+BuHJ,UjCozIA,iBADA,ciChzIE,MAAA,KAGF,UjCmzIA,cEv6II,uBAAA,mBACA,wBAAA,mB+BwHJ,UjCozIA,iBE/5II,2BAAA,mBACA,0BAAA,mB+BuHF,kBACE,cAAA,OxBpGA,yBwBgGJ,YAQI,QAAA,KACA,UAAA,IAAA,KAGA,kBAEE,KAAA,EAAA,EAAA,GACA,cAAA,EAEA,wBACE,YAAA,EACA,YAAA,EAKA,mC/BpJJ,wBAAA,EACA,2BAAA,EF+7IJ,gDiCzyIU,iDAGE,wBAAA,EjC0yIZ,gDiCxyIU,oDAGE,2BAAA,EAIJ,oC/BrJJ,uBAAA,EACA,0BAAA,EF67IJ,iDiCtyIU,kDAGE,uBAAA,EjCuyIZ,iDiCryIU,qDAGE,0BAAA,GC7MZ,kBACE,SAAA,SACA,QAAA,KACA,YAAA,OACA,MAAA,KACA,QAAA,KAAA,QjC4RI,UAAA,KiC1RJ,MAAA,QACA,WAAA,KACA,iBAAA,KACA,OAAA,EhCKE,cAAA,EgCHF,gBAAA,KjBAI,WAAA,MAAA,KAAA,WAAA,CAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,WAAA,CAAA,cAAA,KAAA,KAIA,uCiBhBN,kBjBiBQ,WAAA,MiBFN,kCACE,MAAA,QACA,iBAAA,QACA,WAAA,MAAA,EAAA,KAAA,EAAA,iBAEA,yCACE,iBAAA,gRACA,UAAA,gBAKJ,yBACE,YAAA,EACA,MAAA,QACA,OAAA,QACA,YAAA,KACA,QAAA,GACA,iBAAA,gRACA,kBAAA,UACA,gBAAA,QjBvBE,WAAA,UAAA,IAAA,YAIA,uCiBWJ,yBjBVM,WAAA,MiBsBN,wBACE,QAAA,EAGF,wBACE,QAAA,EACA,aAAA,QACA,QAAA,EACA,WAAA,EAAA,EAAA,EAAA,OAAA,qBAIJ,kBACE,cAAA,EAGF,gBACE,iBAAA,KACA,OAAA,IAAA,MAAA,iBAEA,8BhCnCE,uBAAA,OACA,wBAAA,OgCqCA,gDhCtCA,uBAAA,mBACA,wBAAA,mBgC0CF,oCACE,WAAA,EAIF,6BhClCE,2BAAA,OACA,0BAAA,OgCqCE,yDhCtCF,2BAAA,mBACA,0BAAA,mBgC0CA,iDhC3CA,2BAAA,OACA,0BAAA,OgCgDJ,gBACE,QAAA,KAAA,QASA,qCACE,aAAA,EAGF,iCACE,aAAA,EACA,YAAA,EhCxFA,cAAA,EgC2FA,6CAAgB,WAAA,EAChB,4CAAe,cAAA,EAEf,mDhC9FA,cAAA,EiCnBJ,YACE,QAAA,KACA,UAAA,KACA,QAAA,EAAA,EACA,cAAA,KAEA,WAAA,KAOA,kCACE,aAAA,MAEA,0CACE,MAAA,KACA,cAAA,MACA,MAAA,QACA,QAAA,kCAIJ,wBACE,MAAA,QCzBJ,YACE,QAAA,KhCGA,aAAA,EACA,WAAA,KgCAF,WACE,SAAA,SACA,QAAA,MACA,MAAA,QACA,gBAAA,KACA,iBAAA,KACA,OAAA,IAAA,MAAA,QnBKI,WAAA,MAAA,KAAA,WAAA,CAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YAIA,uCmBfN,WnBgBQ,WAAA,MmBPN,iBACE,QAAA,EACA,MAAA,QAEA,iBAAA,QACA,aAAA,QAGF,iBACE,QAAA,EACA,MAAA,QACA,iBAAA,QACA,QAAA,EACA,WAAA,EAAA,EAAA,EAAA,OAAA,qBAKF,wCACE,YAAA,KAGF,6BACE,QAAA,EACA,MAAA,KlBlCF,iBAAA,QkBoCE,aAAA,QAGF,+BACE,MAAA,QACA,eAAA,KACA,iBAAA,KACA,aAAA,QC3CF,WACE,QAAA,QAAA,OAOI,kCnCqCJ,uBAAA,OACA,0BAAA,OmChCI,iCnCiBJ,wBAAA,OACA,2BAAA,OmChCF,0BACE,QAAA,OAAA,OpCgSE,UAAA,QoCzRE,iDnCqCJ,uBAAA,MACA,0BAAA,MmChCI,gDnCiBJ,wBAAA,MACA,2BAAA,MmChCF,0BACE,QAAA,OAAA,MpCgSE,UAAA,QoCzRE,iDnCqCJ,uBAAA,MACA,0BAAA,MmChCI,gDnCiBJ,wBAAA,MACA,2BAAA,MoC/BJ,OACE,QAAA,aACA,QAAA,MAAA,MrC8RI,UAAA,MqC5RJ,YAAA,IACA,YAAA,EACA,MAAA,KACA,WAAA,OACA,YAAA,OACA,eAAA,SpCKE,cAAA,OoCAF,aACE,QAAA,KAKJ,YACE,SAAA,SACA,IAAA,KCvBF,OACE,SAAA,SACA,QAAA,KAAA,KACA,cAAA,KACA,OAAA,IAAA,MAAA,YrCWE,cAAA,OqCNJ,eAEE,MAAA,QAIF,YACE,YAAA,IAQF,mBACE,cAAA,KAGA,8BACE,SAAA,SACA,IAAA,EACA,MAAA,EACA,QAAA,EACA,QAAA,QAAA,KAeF,eClDA,MAAA,QtBEA,iBAAA,QsBAA,aAAA,QAEA,2BACE,MAAA,QD6CF,iBClDA,MAAA,QtBEA,iBAAA,QsBAA,aAAA,QAEA,6BACE,MAAA,QD6CF,eClDA,MAAA,QtBEA,iBAAA,QsBAA,aAAA,QAEA,2BACE,MAAA,QD6CF,YClDA,MAAA,QtBEA,iBAAA,QsBAA,aAAA,QAEA,wBACE,MAAA,QD6CF,eClDA,MAAA,QtBEA,iBAAA,QsBAA,aAAA,QAEA,2BACE,MAAA,QD6CF,cClDA,MAAA,QtBEA,iBAAA,QsBAA,aAAA,QAEA,0BACE,MAAA,QD6CF,aClDA,MAAA,QtBEA,iBAAA,QsBAA,aAAA,QAEA,yBACE,MAAA,QD6CF,YClDA,MAAA,QtBEA,iBAAA,QsBAA,aAAA,QAEA,wBACE,MAAA,QCHF,wCACE,GAAK,sBAAA,MADP,gCACE,GAAK,sBAAA,MAKT,UACE,QAAA,KACA,OAAA,KACA,SAAA,OxCwRI,UAAA,OwCtRJ,iBAAA,QvCIE,cAAA,OuCCJ,cACE,QAAA,KACA,eAAA,OACA,gBAAA,OACA,SAAA,OACA,MAAA,KACA,WAAA,OACA,YAAA,OACA,iBAAA,QxBZI,WAAA,MAAA,IAAA,KAIA,uCwBAN,cxBCQ,WAAA,MwBWR,sBvBYE,iBAAA,iKuBVA,gBAAA,KAAA,KAIA,uBACE,kBAAA,GAAA,OAAA,SAAA,qBAAA,UAAA,GAAA,OAAA,SAAA,qBAGE,uCAJJ,uBAKM,kBAAA,KAAA,UAAA,MCvCR,YACE,QAAA,KACA,eAAA,OAGA,aAAA,EACA,cAAA,ExCSE,cAAA,OwCLJ,qBACE,gBAAA,KACA,cAAA,QAEA,gCAEE,QAAA,uBAAA,KACA,kBAAA,QAUJ,wBACE,MAAA,KACA,MAAA,QACA,WAAA,QAGA,8BAAA,8BAEE,QAAA,EACA,MAAA,QACA,gBAAA,KACA,iBAAA,QAGF,+BACE,MAAA,QACA,iBAAA,QASJ,iBACE,SAAA,SACA,QAAA,MACA,QAAA,MAAA,KACA,MAAA,QACA,gBAAA,KACA,iBAAA,KACA,OAAA,IAAA,MAAA,iBAEA,6BxCrCE,uBAAA,QACA,wBAAA,QwCwCF,4BxC3BE,2BAAA,QACA,0BAAA,QwC8BF,0BAAA,0BAEE,MAAA,QACA,eAAA,KACA,iBAAA,KAIF,wBACE,QAAA,EACA,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,kCACE,iBAAA,EAEA,yCACE,WAAA,KACA,iBAAA,IAcF,uBACE,eAAA,IAGE,oDxCrCJ,0BAAA,OAZA,wBAAA,EwCsDI,mDxCtDJ,wBAAA,OAYA,0BAAA,EwC+CI,+CACE,WAAA,EAGF,yDACE,iBAAA,IACA,kBAAA,EAEA,gEACE,YAAA,KACA,kBAAA,IjCpER,yBiC4CA,0BACE,eAAA,IAGE,uDxCrCJ,0BAAA,OAZA,wBAAA,EwCsDI,sDxCtDJ,wBAAA,OAYA,0BAAA,EwC+CI,kDACE,WAAA,EAGF,4DACE,iBAAA,IACA,kBAAA,EAEA,mEACE,YAAA,KACA,kBAAA,KjCpER,yBiC4CA,0BACE,eAAA,IAGE,uDxCrCJ,0BAAA,OAZA,wBAAA,EwCsDI,sDxCtDJ,wBAAA,OAYA,0BAAA,EwC+CI,kDACE,WAAA,EAGF,4DACE,iBAAA,IACA,kBAAA,EAEA,mEACE,YAAA,KACA,kBAAA,KjCpER,yBiC4CA,0BACE,eAAA,IAGE,uDxCrCJ,0BAAA,OAZA,wBAAA,EwCsDI,sDxCtDJ,wBAAA,OAYA,0BAAA,EwC+CI,kDACE,WAAA,EAGF,4DACE,iBAAA,IACA,kBAAA,EAEA,mEACE,YAAA,KACA,kBAAA,KjCpER,0BiC4CA,0BACE,eAAA,IAGE,uDxCrCJ,0BAAA,OAZA,wBAAA,EwCsDI,sDxCtDJ,wBAAA,OAYA,0BAAA,EwC+CI,kDACE,WAAA,EAGF,4DACE,iBAAA,IACA,kBAAA,EAEA,mEACE,YAAA,KACA,kBAAA,KjCpER,0BiC4CA,2BACE,eAAA,IAGE,wDxCrCJ,0BAAA,OAZA,wBAAA,EwCsDI,uDxCtDJ,wBAAA,OAYA,0BAAA,EwC+CI,mDACE,WAAA,EAGF,6DACE,iBAAA,IACA,kBAAA,EAEA,oEACE,YAAA,KACA,kBAAA,KAcZ,kBxC9HI,cAAA,EwCiIF,mCACE,aAAA,EAAA,EAAA,IAEA,8CACE,oBAAA,ECpJJ,yBACE,MAAA,QACA,iBAAA,QAGE,sDAAA,sDAEE,MAAA,QACA,iBAAA,QAGF,uDACE,MAAA,KACA,iBAAA,QACA,aAAA,QAdN,2BACE,MAAA,QACA,iBAAA,QAGE,wDAAA,wDAEE,MAAA,QACA,iBAAA,QAGF,yDACE,MAAA,KACA,iBAAA,QACA,aAAA,QAdN,yBACE,MAAA,QACA,iBAAA,QAGE,sDAAA,sDAEE,MAAA,QACA,iBAAA,QAGF,uDACE,MAAA,KACA,iBAAA,QACA,aAAA,QAdN,sBACE,MAAA,QACA,iBAAA,QAGE,mDAAA,mDAEE,MAAA,QACA,iBAAA,QAGF,oDACE,MAAA,KACA,iBAAA,QACA,aAAA,QAdN,yBACE,MAAA,QACA,iBAAA,QAGE,sDAAA,sDAEE,MAAA,QACA,iBAAA,QAGF,uDACE,MAAA,KACA,iBAAA,QACA,aAAA,QAdN,wBACE,MAAA,QACA,iBAAA,QAGE,qDAAA,qDAEE,MAAA,QACA,iBAAA,QAGF,sDACE,MAAA,KACA,iBAAA,QACA,aAAA,QAdN,uBACE,MAAA,QACA,iBAAA,QAGE,oDAAA,oDAEE,MAAA,QACA,iBAAA,QAGF,qDACE,MAAA,KACA,iBAAA,QACA,aAAA,QAdN,sBACE,MAAA,QACA,iBAAA,QAGE,mDAAA,mDAEE,MAAA,QACA,iBAAA,QAGF,oDACE,MAAA,KACA,iBAAA,QACA,aAAA,QCbR,WACE,WAAA,YACA,MAAA,IACA,OAAA,IACA,QAAA,MAAA,MACA,MAAA,KACA,WAAA,YAAA,0TAAA,MAAA,CAAA,IAAA,KAAA,UACA,OAAA,E1COE,cAAA,O0CLF,QAAA,GAGA,iBACE,MAAA,KACA,gBAAA,KACA,QAAA,IAGF,iBACE,QAAA,EACA,WAAA,EAAA,EAAA,EAAA,OAAA,qBACA,QAAA,EAGF,oBAAA,oBAEE,eAAA,KACA,oBAAA,KAAA,iBAAA,KAAA,YAAA,KACA,QAAA,IAIJ,iBACE,OAAA,UAAA,gBAAA,iBCtCF,OACE,MAAA,MACA,UAAA,K5CmSI,UAAA,Q4ChSJ,eAAA,KACA,iBAAA,sBACA,gBAAA,YACA,OAAA,IAAA,MAAA,eACA,WAAA,EAAA,MAAA,KAAA,gB3CUE,cAAA,O2CPF,eACE,QAAA,EAGF,kBACE,QAAA,KAIJ,iBACE,MAAA,oBAAA,MAAA,iBAAA,MAAA,YACA,UAAA,KACA,eAAA,KAEA,mCACE,cAAA,OAIJ,cACE,QAAA,KACA,YAAA,OACA,QAAA,MAAA,OACA,MAAA,QACA,iBAAA,sBACA,gBAAA,YACA,cAAA,IAAA,MAAA,gB3CVE,uBAAA,mBACA,wBAAA,mB2CYF,yBACE,aAAA,SACA,YAAA,OAIJ,YACE,QAAA,OACA,UAAA,WC1CF,OACE,SAAA,MACA,IAAA,EACA,KAAA,EACA,QAAA,KACA,QAAA,KACA,MAAA,KACA,OAAA,KACA,WAAA,OACA,WAAA,KAGA,QAAA,EAOF,cACE,SAAA,SACA,MAAA,KACA,OAAA,MAEA,eAAA,KAGA,0B7BlBI,WAAA,UAAA,IAAA,S6BoBF,UAAA,mB7BhBE,uC6BcJ,0B7BbM,WAAA,M6BiBN,0BACE,UAAA,KAIF,kCACE,UAAA,YAIJ,yBACE,OAAA,kBAEA,wCACE,WAAA,KACA,SAAA,OAGF,qCACE,WAAA,KAIJ,uBACE,QAAA,KACA,YAAA,OACA,WAAA,kBAIF,eACE,SAAA,SACA,QAAA,KACA,eAAA,OACA,MAAA,KAGA,eAAA,KACA,iBAAA,KACA,gBAAA,YACA,OAAA,IAAA,MAAA,e5C3DE,cAAA,M4C+DF,QAAA,EAIF,gBCpFE,SAAA,MACA,IAAA,EACA,KAAA,EACA,QAAA,KACA,MAAA,MACA,OAAA,MACA,iBAAA,KAGA,qBAAS,QAAA,EACT,qBAAS,QAAA,GDgFX,cACE,QAAA,KACA,YAAA,EACA,YAAA,OACA,gBAAA,cACA,QAAA,KAAA,KACA,cAAA,IAAA,MAAA,Q5CtEE,uBAAA,kBACA,wBAAA,kB4CwEF,yBACE,QAAA,MAAA,MACA,OAAA,OAAA,OAAA,OAAA,KAKJ,aACE,cAAA,EACA,YAAA,IAKF,YACE,SAAA,SAGA,KAAA,EAAA,EAAA,KACA,QAAA,KAIF,cACE,QAAA,KACA,UAAA,KACA,YAAA,EACA,YAAA,OACA,gBAAA,SACA,QAAA,OACA,WAAA,IAAA,MAAA,Q5CzFE,2BAAA,kBACA,0BAAA,kB4C8FF,gBACE,OAAA,OrC3EA,yBqCkFF,cACE,UAAA,MACA,OAAA,QAAA,KAGF,yBACE,OAAA,oBAGF,uBACE,WAAA,oBAOF,UAAY,UAAA,OrCnGV,yBqCuGF,U9CywKF,U8CvwKI,UAAA,OrCzGA,0BqC8GF,UAAY,UAAA,QASV,kBACE,MAAA,MACA,UAAA,KACA,OAAA,KACA,OAAA,EAEA,iCACE,OAAA,KACA,OAAA,E5C3KJ,cAAA,E4C+KE,gC5C/KF,cAAA,E4CmLE,8BACE,WAAA,KAGF,gC5CvLF,cAAA,EOyDA,4BqC0GA,0BACE,MAAA,MACA,UAAA,KACA,OAAA,KACA,OAAA,EAEA,yCACE,OAAA,KACA,OAAA,E5C3KJ,cAAA,E4C+KE,wC5C/KF,cAAA,E4CmLE,sCACE,WAAA,KAGF,wC5CvLF,cAAA,GOyDA,4BqC0GA,0BACE,MAAA,MACA,UAAA,KACA,OAAA,KACA,OAAA,EAEA,yCACE,OAAA,KACA,OAAA,E5C3KJ,cAAA,E4C+KE,wC5C/KF,cAAA,E4CmLE,sCACE,WAAA,KAGF,wC5CvLF,cAAA,GOyDA,4BqC0GA,0BACE,MAAA,MACA,UAAA,KACA,OAAA,KACA,OAAA,EAEA,yCACE,OAAA,KACA,OAAA,E5C3KJ,cAAA,E4C+KE,wC5C/KF,cAAA,E4CmLE,sCACE,WAAA,KAGF,wC5CvLF,cAAA,GOyDA,6BqC0GA,0BACE,MAAA,MACA,UAAA,KACA,OAAA,KACA,OAAA,EAEA,yCACE,OAAA,KACA,OAAA,E5C3KJ,cAAA,E4C+KE,wC5C/KF,cAAA,E4CmLE,sCACE,WAAA,KAGF,wC5CvLF,cAAA,GOyDA,6BqC0GA,2BACE,MAAA,MACA,UAAA,KACA,OAAA,KACA,OAAA,EAEA,0CACE,OAAA,KACA,OAAA,E5C3KJ,cAAA,E4C+KE,yC5C/KF,cAAA,E4CmLE,uCACE,WAAA,KAGF,yC5CvLF,cAAA,G8ClBJ,SACE,SAAA,SACA,QAAA,KACA,QAAA,MACA,OAAA,ECJA,YAAA,0BAEA,WAAA,OACA,YAAA,IACA,YAAA,IACA,WAAA,KACA,WAAA,MACA,gBAAA,KACA,YAAA,KACA,eAAA,KACA,eAAA,OACA,WAAA,OACA,aAAA,OACA,YAAA,OACA,WAAA,KhDsRI,UAAA,Q+C1RJ,UAAA,WACA,QAAA,EAEA,cAAS,QAAA,GAET,wBACE,SAAA,SACA,QAAA,MACA,MAAA,MACA,OAAA,MAEA,gCACE,SAAA,SACA,QAAA,GACA,aAAA,YACA,aAAA,MAKN,6CAAA,gBACE,QAAA,MAAA,EAEA,4DAAA,+BACE,OAAA,EAEA,oEAAA,uCACE,IAAA,KACA,aAAA,MAAA,MAAA,EACA,iBAAA,KAKN,+CAAA,gBACE,QAAA,EAAA,MAEA,8DAAA,+BACE,KAAA,EACA,MAAA,MACA,OAAA,MAEA,sEAAA,uCACE,MAAA,KACA,aAAA,MAAA,MAAA,MAAA,EACA,mBAAA,KAKN,gDAAA,mBACE,QAAA,MAAA,EAEA,+DAAA,kCACE,IAAA,EAEA,uEAAA,0CACE,OAAA,KACA,aAAA,EAAA,MAAA,MACA,oBAAA,KAKN,8CAAA,kBACE,QAAA,EAAA,MAEA,6DAAA,iCACE,MAAA,EACA,MAAA,MACA,OAAA,MAEA,qEAAA,yCACE,KAAA,KACA,aAAA,MAAA,EAAA,MAAA,MACA,kBAAA,KAqBN,eACE,UAAA,MACA,QAAA,OAAA,MACA,MAAA,KACA,WAAA,OACA,iBAAA,K9C7FE,cAAA,OgDnBJ,SACE,SAAA,SACA,IAAA,EACA,KAAA,EACA,QAAA,KACA,QAAA,MACA,UAAA,MDLA,YAAA,0BAEA,WAAA,OACA,YAAA,IACA,YAAA,IACA,WAAA,KACA,WAAA,MACA,gBAAA,KACA,YAAA,KACA,eAAA,KACA,eAAA,OACA,WAAA,OACA,aAAA,OACA,YAAA,OACA,WAAA,KhDsRI,UAAA,QiDzRJ,UAAA,WACA,iBAAA,KACA,gBAAA,YACA,OAAA,IAAA,MAAA,ehDIE,cAAA,MgDAF,wBACE,SAAA,SACA,QAAA,MACA,MAAA,KACA,OAAA,MAEA,+BAAA,gCAEE,SAAA,SACA,QAAA,MACA,QAAA,GACA,aAAA,YACA,aAAA,MAMJ,4DAAA,+BACE,OAAA,mBAEA,oEAAA,uCACE,OAAA,EACA,aAAA,MAAA,MAAA,EACA,iBAAA,gBAGF,mEAAA,sCACE,OAAA,IACA,aAAA,MAAA,MAAA,EACA,iBAAA,KAMJ,8DAAA,+BACE,KAAA,mBACA,MAAA,MACA,OAAA,KAEA,sEAAA,uCACE,KAAA,EACA,aAAA,MAAA,MAAA,MAAA,EACA,mBAAA,gBAGF,qEAAA,sCACE,KAAA,IACA,aAAA,MAAA,MAAA,MAAA,EACA,mBAAA,KAMJ,+DAAA,kCACE,IAAA,mBAEA,uEAAA,0CACE,IAAA,EACA,aAAA,EAAA,MAAA,MAAA,MACA,oBAAA,gBAGF,sEAAA,yCACE,IAAA,IACA,aAAA,EAAA,MAAA,MAAA,MACA,oBAAA,KAKJ,wEAAA,2CACE,SAAA,SACA,IAAA,EACA,KAAA,IACA,QAAA,MACA,MAAA,KACA,YAAA,OACA,QAAA,GACA,cAAA,IAAA,MAAA,QAKF,6DAAA,iCACE,MAAA,mBACA,MAAA,MACA,OAAA,KAEA,qEAAA,yCACE,MAAA,EACA,aAAA,MAAA,EAAA,MAAA,MACA,kBAAA,gBAGF,oEAAA,wCACE,MAAA,IACA,aAAA,MAAA,EAAA,MAAA,MACA,kBAAA,KAqBN,gBACE,QAAA,MAAA,KACA,cAAA,EjDuJI,UAAA,KiDpJJ,iBAAA,QACA,cAAA,IAAA,MAAA,ehDtHE,uBAAA,kBACA,wBAAA,kBgDwHF,sBACE,QAAA,KAIJ,cACE,QAAA,KAAA,KACA,MAAA,QC/IF,UACE,SAAA,SAGF,wBACE,aAAA,MAGF,gBACE,SAAA,SACA,MAAA,KACA,SAAA,OCtBA,uBACE,QAAA,MACA,MAAA,KACA,QAAA,GDuBJ,eACE,SAAA,SACA,QAAA,KACA,MAAA,KACA,MAAA,KACA,aAAA,MACA,4BAAA,OAAA,oBAAA,OlClBI,WAAA,UAAA,IAAA,YAIA,uCkCQN,elCPQ,WAAA,MjBgzLR,oBACA,oBmDhyLA,sBAGE,QAAA,MnDmyLF,0BmD/xLA,8CAEE,UAAA,iBnDkyLF,4BmD/xLA,4CAEE,UAAA,kBAWA,8BACE,QAAA,EACA,oBAAA,QACA,UAAA,KnD0xLJ,uDACA,qDmDxxLE,qCAGE,QAAA,EACA,QAAA,EnDyxLJ,yCmDtxLE,2CAEE,QAAA,EACA,QAAA,ElC/DE,WAAA,QAAA,GAAA,IAIA,uCjBq1LN,yCmD7xLE,2ClCvDM,WAAA,MjB01LR,uBmDtxLA,uBAEE,SAAA,SACA,IAAA,EACA,OAAA,EACA,QAAA,EAEA,QAAA,KACA,YAAA,OACA,gBAAA,OACA,MAAA,IACA,QAAA,EACA,MAAA,KACA,WAAA,OACA,WAAA,IACA,OAAA,EACA,QAAA,GlCzFI,WAAA,QAAA,KAAA,KAIA,uCjB82LN,uBmDzyLA,uBlCpEQ,WAAA,MjBm3LR,6BADA,6BmD1xLE,6BAAA,6BAEE,MAAA,KACA,gBAAA,KACA,QAAA,EACA,QAAA,GAGJ,uBACE,KAAA,EAGF,uBACE,MAAA,EnD8xLF,4BmDzxLA,4BAEE,QAAA,aACA,MAAA,KACA,OAAA,KACA,kBAAA,UACA,oBAAA,IACA,gBAAA,KAAA,KAWF,4BACE,iBAAA,wPAEF,4BACE,iBAAA,yPAQF,qBACE,SAAA,SACA,MAAA,EACA,OAAA,EACA,KAAA,EACA,QAAA,EACA,QAAA,KACA,gBAAA,OACA,QAAA,EAEA,aAAA,IACA,cAAA,KACA,YAAA,IACA,WAAA,KAEA,sCACE,WAAA,YACA,KAAA,EAAA,EAAA,KACA,MAAA,KACA,OAAA,IACA,QAAA,EACA,aAAA,IACA,YAAA,IACA,YAAA,OACA,OAAA,QACA,iBAAA,KACA,gBAAA,YACA,OAAA,EAEA,WAAA,KAAA,MAAA,YACA,cAAA,KAAA,MAAA,YACA,QAAA,GlC5KE,WAAA,QAAA,IAAA,KAIA,uCkCwJJ,sClCvJM,WAAA,MkC2KN,6BACE,QAAA,EASJ,kBACE,SAAA,SACA,MAAA,IACA,OAAA,QACA,KAAA,IACA,YAAA,QACA,eAAA,QACA,MAAA,KACA,WAAA,OnDoxLF,2CmD9wLE,2CAEE,OAAA,UAAA,eAGF,qDACE,iBAAA,KAGF,iCACE,MAAA,KE7NJ,kCACE,GAAK,UAAA,gBADP,0BACE,GAAK,UAAA,gBAIP,gBACE,QAAA,aACA,MAAA,KACA,OAAA,KACA,eAAA,QACA,OAAA,MAAA,MAAA,aACA,mBAAA,YAEA,cAAA,IACA,kBAAA,KAAA,OAAA,SAAA,eAAA,UAAA,KAAA,OAAA,SAAA,eAGF,mBACE,MAAA,KACA,OAAA,KACA,aAAA,KAQF,gCACE,GACE,UAAA,SAEF,IACE,QAAA,EACA,UAAA,MANJ,wBACE,GACE,UAAA,SAEF,IACE,QAAA,EACA,UAAA,MAKJ,cACE,QAAA,aACA,MAAA,KACA,OAAA,KACA,eAAA,QACA,iBAAA,aAEA,cAAA,IACA,QAAA,EACA,kBAAA,KAAA,OAAA,SAAA,aAAA,UAAA,KAAA,OAAA,SAAA,aAGF,iBACE,MAAA,KACA,OAAA,KAIA,uCACE,gBrDo/LJ,cqDl/LM,2BAAA,KAAA,mBAAA,MCjEN,WACE,SAAA,MACA,OAAA,EACA,QAAA,KACA,QAAA,KACA,eAAA,OACA,UAAA,KAEA,WAAA,OACA,iBAAA,KACA,gBAAA,YACA,QAAA,ErCKI,WAAA,UAAA,IAAA,YAIA,uCqCpBN,WrCqBQ,WAAA,MqCLR,oBPdE,SAAA,MACA,IAAA,EACA,KAAA,EACA,QAAA,KACA,MAAA,MACA,OAAA,MACA,iBAAA,KAGA,yBAAS,QAAA,EACT,yBAAS,QAAA,GOQX,kBACE,QAAA,KACA,YAAA,OACA,gBAAA,cACA,QAAA,KAAA,KAEA,6BACE,QAAA,MAAA,MACA,WAAA,OACA,aAAA,OACA,cAAA,OAIJ,iBACE,cAAA,EACA,YAAA,IAGF,gBACE,UAAA,EACA,QAAA,KAAA,KACA,WAAA,KAGF,iBACE,IAAA,EACA,KAAA,EACA,MAAA,MACA,aAAA,IAAA,MAAA,eACA,UAAA,kBAGF,eACE,IAAA,EACA,MAAA,EACA,MAAA,MACA,YAAA,IAAA,MAAA,eACA,UAAA,iBAGF,eACE,IAAA,EACA,MAAA,EACA,KAAA,EACA,OAAA,KACA,WAAA,KACA,cAAA,IAAA,MAAA,eACA,UAAA,kBAGF,kBACE,MAAA,EACA,KAAA,EACA,OAAA,KACA,WAAA,KACA,WAAA,IAAA,MAAA,eACA,UAAA,iBAGF,gBACE,UAAA,KCjFF,aACE,QAAA,aACA,WAAA,IACA,eAAA,OACA,OAAA,KACA,iBAAA,aACA,QAAA,GAEA,yBACE,QAAA,aACA,QAAA,GAKJ,gBACE,WAAA,KAGF,gBACE,WAAA,KAGF,gBACE,WAAA,MAKA,+BACE,kBAAA,iBAAA,GAAA,YAAA,SAAA,UAAA,iBAAA,GAAA,YAAA,SAIJ,oCACE,IACE,QAAA,IAFJ,4BACE,IACE,QAAA,IAIJ,kBACE,mBAAA,8DAAA,WAAA,8DACA,kBAAA,KAAA,KAAA,UAAA,KAAA,KACA,kBAAA,iBAAA,GAAA,OAAA,SAAA,UAAA,iBAAA,GAAA,OAAA,SAGF,oCACE,KACE,sBAAA,MAAA,GAAA,cAAA,MAAA,IAFJ,4BACE,KACE,sBAAA,MAAA,GAAA,cAAA,MAAA,IH9CF,iBACE,QAAA,MACA,MAAA,KACA,QAAA,GIJF,cACE,MAAA,QAGE,oBAAA,oBAEE,MAAA,QANN,gBACE,MAAA,QAGE,sBAAA,sBAEE,MAAA,QANN,cACE,MAAA,QAGE,oBAAA,oBAEE,MAAA,QANN,WACE,MAAA,QAGE,iBAAA,iBAEE,MAAA,QANN,cACE,MAAA,QAGE,oBAAA,oBAEE,MAAA,QANN,aACE,MAAA,QAGE,mBAAA,mBAEE,MAAA,QANN,YACE,MAAA,QAGE,kBAAA,kBAEE,MAAA,QANN,WACE,MAAA,QAGE,iBAAA,iBAEE,MAAA,QCLR,OACE,SAAA,SACA,MAAA,KAEA,eACE,QAAA,MACA,YAAA,uBACA,QAAA,GAGF,SACE,SAAA,SACA,IAAA,EACA,KAAA,EACA,MAAA,KACA,OAAA,KAKF,WACE,kBAAA,KADF,WACE,kBAAA,mBADF,YACE,kBAAA,oBADF,YACE,kBAAA,oBCrBJ,WACE,SAAA,MACA,IAAA,EACA,MAAA,EACA,KAAA,EACA,QAAA,KAGF,cACE,SAAA,MACA,MAAA,EACA,OAAA,EACA,KAAA,EACA,QAAA,KAQE,YACE,SAAA,eAAA,SAAA,OACA,IAAA,EACA,QAAA,KjDqCF,yBiDxCA,eACE,SAAA,eAAA,SAAA,OACA,IAAA,EACA,QAAA,MjDqCF,yBiDxCA,eACE,SAAA,eAAA,SAAA,OACA,IAAA,EACA,QAAA,MjDqCF,yBiDxCA,eACE,SAAA,eAAA,SAAA,OACA,IAAA,EACA,QAAA,MjDqCF,0BiDxCA,eACE,SAAA,eAAA,SAAA,OACA,IAAA,EACA,QAAA,MjDqCF,0BiDxCA,gBACE,SAAA,eAAA,SAAA,OACA,IAAA,EACA,QAAA,MCzBN,QACE,QAAA,KACA,eAAA,IACA,YAAA,OACA,WAAA,QAGF,QACE,QAAA,KACA,KAAA,EAAA,EAAA,KACA,eAAA,OACA,WAAA,QCRF,iB5Dk4MA,0D6D93ME,SAAA,mBACA,MAAA,cACA,OAAA,cACA,QAAA,YACA,OAAA,eACA,SAAA,iBACA,KAAA,wBACA,YAAA,iBACA,OAAA,YCXA,uBACE,SAAA,SACA,IAAA,EACA,MAAA,EACA,OAAA,EACA,KAAA,EACA,QAAA,EACA,QAAA,GCRJ,eCAE,SAAA,OACA,cAAA,SACA,YAAA,OCNF,IACE,QAAA,aACA,WAAA,QACA,MAAA,IACA,WAAA,IACA,iBAAA,aACA,QAAA,ICyDM,gBAOI,eAAA,mBAPJ,WAOI,eAAA,cAPJ,cAOI,eAAA,iBAPJ,cAOI,eAAA,iBAPJ,mBAOI,eAAA,sBAPJ,gBAOI,eAAA,mBAPJ,aAOI,MAAA,eAPJ,WAOI,MAAA,gBAPJ,YAOI,MAAA,eAPJ,WAOI,QAAA,YAPJ,YAOI,QAAA,cAPJ,YAOI,QAAA,aAPJ,YAOI,QAAA,cAPJ,aAOI,QAAA,YAPJ,eAOI,SAAA,eAPJ,iBAOI,SAAA,iBAPJ,kBAOI,SAAA,kBAPJ,iBAOI,SAAA,iBAPJ,UAOI,QAAA,iBAPJ,gBAOI,QAAA,uBAPJ,SAOI,QAAA,gBAPJ,QAOI,QAAA,eAPJ,SAOI,QAAA,gBAPJ,aAOI,QAAA,oBAPJ,cAOI,QAAA,qBAPJ,QAOI,QAAA,eAPJ,eAOI,QAAA,sBAPJ,QAOI,QAAA,eAPJ,QAOI,WAAA,EAAA,MAAA,KAAA,0BAPJ,WAOI,WAAA,EAAA,QAAA,OAAA,2BAPJ,WAOI,WAAA,EAAA,KAAA,KAAA,2BAPJ,aAOI,WAAA,eAPJ,iBAOI,SAAA,iBAPJ,mBAOI,SAAA,mBAPJ,mBAOI,SAAA,mBAPJ,gBAOI,SAAA,gBAPJ,iBAOI,SAAA,yBAAA,SAAA,iBAPJ,OAOI,IAAA,YAPJ,QAOI,IAAA,cAPJ,SAOI,IAAA,eAPJ,UAOI,OAAA,YAPJ,WAOI,OAAA,cAPJ,YAOI,OAAA,eAPJ,SAOI,KAAA,YAPJ,UAOI,KAAA,cAPJ,WAOI,KAAA,eAPJ,OAOI,MAAA,YAPJ,QAOI,MAAA,cAPJ,SAOI,MAAA,eAPJ,kBAOI,UAAA,+BAPJ,oBAOI,UAAA,2BAPJ,oBAOI,UAAA,2BAPJ,QAOI,OAAA,IAAA,MAAA,kBAPJ,UAOI,OAAA,YAPJ,YAOI,WAAA,IAAA,MAAA,kBAPJ,cAOI,WAAA,YAPJ,YAOI,aAAA,IAAA,MAAA,kBAPJ,cAOI,aAAA,YAPJ,eAOI,cAAA,IAAA,MAAA,kBAPJ,iBAOI,cAAA,YAPJ,cAOI,YAAA,IAAA,MAAA,kBAPJ,gBAOI,YAAA,YAPJ,gBAOI,aAAA,kBAPJ,kBAOI,aAAA,kBAPJ,gBAOI,aAAA,kBAPJ,aAOI,aAAA,kBAPJ,gBAOI,aAAA,kBAPJ,eAOI,aAAA,kBAPJ,cAOI,aAAA,kBAPJ,aAOI,aAAA,kBAPJ,cAOI,aAAA,eAPJ,UAOI,aAAA,cAPJ,UAOI,aAAA,cAPJ,UAOI,aAAA,cAPJ,UAOI,aAAA,cAPJ,UAOI,aAAA,cAPJ,MAOI,MAAA,cAPJ,MAOI,MAAA,cAPJ,MAOI,MAAA,cAPJ,OAOI,MAAA,eAPJ,QAOI,MAAA,eAPJ,QAOI,UAAA,eAPJ,QAOI,MAAA,gBAPJ,YAOI,UAAA,gBAPJ,MAOI,OAAA,cAPJ,MAOI,OAAA,cAPJ,MAOI,OAAA,cAPJ,OAOI,OAAA,eAPJ,QAOI,OAAA,eAPJ,QAOI,WAAA,eAPJ,QAOI,OAAA,gBAPJ,YAOI,WAAA,gBAPJ,WAOI,KAAA,EAAA,EAAA,eAPJ,UAOI,eAAA,cAPJ,aAOI,eAAA,iBAPJ,kBAOI,eAAA,sBAPJ,qBAOI,eAAA,yBAPJ,aAOI,UAAA,YAPJ,aAOI,UAAA,YAPJ,eAOI,YAAA,YAPJ,eAOI,YAAA,YAPJ,WAOI,UAAA,eAPJ,aAOI,UAAA,iBAPJ,mBAOI,UAAA,uBAPJ,OAOI,IAAA,YAPJ,OAOI,IAAA,iBAPJ,OAOI,IAAA,gBAPJ,OAOI,IAAA,eAPJ,OAOI,IAAA,iBAPJ,OAOI,IAAA,eAPJ,uBAOI,gBAAA,qBAPJ,qBAOI,gBAAA,mBAPJ,wBAOI,gBAAA,iBAPJ,yBAOI,gBAAA,wBAPJ,wBAOI,gBAAA,uBAPJ,wBAOI,gBAAA,uBAPJ,mBAOI,YAAA,qBAPJ,iBAOI,YAAA,mBAPJ,oBAOI,YAAA,iBAPJ,sBAOI,YAAA,mBAPJ,qBAOI,YAAA,kBAPJ,qBAOI,cAAA,qBAPJ,mBAOI,cAAA,mBAPJ,sBAOI,cAAA,iBAPJ,uBAOI,cAAA,wBAPJ,sBAOI,cAAA,uBAPJ,uBAOI,cAAA,kBAPJ,iBAOI,WAAA,eAPJ,kBAOI,WAAA,qBAPJ,gBAOI,WAAA,mBAPJ,mBAOI,WAAA,iBAPJ,qBAOI,WAAA,mBAPJ,oBAOI,WAAA,kBAPJ,aAOI,MAAA,aAPJ,SAOI,MAAA,YAPJ,SAOI,MAAA,YAPJ,SAOI,MAAA,YAPJ,SAOI,MAAA,YAPJ,SAOI,MAAA,YAPJ,SAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,KAOI,OAAA,YAPJ,KAOI,OAAA,iBAPJ,KAOI,OAAA,gBAPJ,KAOI,OAAA,eAPJ,KAOI,OAAA,iBAPJ,KAOI,OAAA,eAPJ,QAOI,OAAA,eAPJ,MAOI,aAAA,YAAA,YAAA,YAPJ,MAOI,aAAA,iBAAA,YAAA,iBAPJ,MAOI,aAAA,gBAAA,YAAA,gBAPJ,MAOI,aAAA,eAAA,YAAA,eAPJ,MAOI,aAAA,iBAAA,YAAA,iBAPJ,MAOI,aAAA,eAAA,YAAA,eAPJ,SAOI,aAAA,eAAA,YAAA,eAPJ,MAOI,WAAA,YAAA,cAAA,YAPJ,MAOI,WAAA,iBAAA,cAAA,iBAPJ,MAOI,WAAA,gBAAA,cAAA,gBAPJ,MAOI,WAAA,eAAA,cAAA,eAPJ,MAOI,WAAA,iBAAA,cAAA,iBAPJ,MAOI,WAAA,eAAA,cAAA,eAPJ,SAOI,WAAA,eAAA,cAAA,eAPJ,MAOI,WAAA,YAPJ,MAOI,WAAA,iBAPJ,MAOI,WAAA,gBAPJ,MAOI,WAAA,eAPJ,MAOI,WAAA,iBAPJ,MAOI,WAAA,eAPJ,SAOI,WAAA,eAPJ,MAOI,aAAA,YAPJ,MAOI,aAAA,iBAPJ,MAOI,aAAA,gBAPJ,MAOI,aAAA,eAPJ,MAOI,aAAA,iBAPJ,MAOI,aAAA,eAPJ,SAOI,aAAA,eAPJ,MAOI,cAAA,YAPJ,MAOI,cAAA,iBAPJ,MAOI,cAAA,gBAPJ,MAOI,cAAA,eAPJ,MAOI,cAAA,iBAPJ,MAOI,cAAA,eAPJ,SAOI,cAAA,eAPJ,MAOI,YAAA,YAPJ,MAOI,YAAA,iBAPJ,MAOI,YAAA,gBAPJ,MAOI,YAAA,eAPJ,MAOI,YAAA,iBAPJ,MAOI,YAAA,eAPJ,SAOI,YAAA,eAPJ,KAOI,QAAA,YAPJ,KAOI,QAAA,iBAPJ,KAOI,QAAA,gBAPJ,KAOI,QAAA,eAPJ,KAOI,QAAA,iBAPJ,KAOI,QAAA,eAPJ,MAOI,cAAA,YAAA,aAAA,YAPJ,MAOI,cAAA,iBAAA,aAAA,iBAPJ,MAOI,cAAA,gBAAA,aAAA,gBAPJ,MAOI,cAAA,eAAA,aAAA,eAPJ,MAOI,cAAA,iBAAA,aAAA,iBAPJ,MAOI,cAAA,eAAA,aAAA,eAPJ,MAOI,YAAA,YAAA,eAAA,YAPJ,MAOI,YAAA,iBAAA,eAAA,iBAPJ,MAOI,YAAA,gBAAA,eAAA,gBAPJ,MAOI,YAAA,eAAA,eAAA,eAPJ,MAOI,YAAA,iBAAA,eAAA,iBAPJ,MAOI,YAAA,eAAA,eAAA,eAPJ,MAOI,YAAA,YAPJ,MAOI,YAAA,iBAPJ,MAOI,YAAA,gBAPJ,MAOI,YAAA,eAPJ,MAOI,YAAA,iBAPJ,MAOI,YAAA,eAPJ,MAOI,cAAA,YAPJ,MAOI,cAAA,iBAPJ,MAOI,cAAA,gBAPJ,MAOI,cAAA,eAPJ,MAOI,cAAA,iBAPJ,MAOI,cAAA,eAPJ,MAOI,eAAA,YAPJ,MAOI,eAAA,iBAPJ,MAOI,eAAA,gBAPJ,MAOI,eAAA,eAPJ,MAOI,eAAA,iBAPJ,MAOI,eAAA,eAPJ,MAOI,aAAA,YAPJ,MAOI,aAAA,iBAPJ,MAOI,aAAA,gBAPJ,MAOI,aAAA,eAPJ,MAOI,aAAA,iBAPJ,MAOI,aAAA,eAPJ,gBAOI,YAAA,mCAPJ,MAOI,UAAA,iCAPJ,MAOI,UAAA,gCAPJ,MAOI,UAAA,8BAPJ,MAOI,UAAA,gCAPJ,MAOI,UAAA,kBAPJ,MAOI,UAAA,eAPJ,YAOI,WAAA,iBAPJ,YAOI,WAAA,iBAPJ,UAOI,YAAA,cAPJ,YAOI,YAAA,kBAPJ,WAOI,YAAA,cAPJ,SAOI,YAAA,cAPJ,WAOI,YAAA,iBAPJ,MAOI,YAAA,YAPJ,OAOI,YAAA,eAPJ,SAOI,YAAA,cAPJ,OAOI,YAAA,YAPJ,YAOI,WAAA,eAPJ,UAOI,WAAA,gBAPJ,aAOI,WAAA,iBAPJ,sBAOI,gBAAA,eAPJ,2BAOI,gBAAA,oBAPJ,8BAOI,gBAAA,uBAPJ,gBAOI,eAAA,oBAPJ,gBAOI,eAAA,oBAPJ,iBAOI,eAAA,qBAPJ,WAOI,YAAA,iBAPJ,aAOI,YAAA,iBAPJ,YAOI,UAAA,qBAAA,WAAA,qBAPJ,cAIQ,kBAAA,EAGJ,MAAA,6DAPJ,gBAIQ,kBAAA,EAGJ,MAAA,+DAPJ,cAIQ,kBAAA,EAGJ,MAAA,6DAPJ,WAIQ,kBAAA,EAGJ,MAAA,0DAPJ,cAIQ,kBAAA,EAGJ,MAAA,6DAPJ,aAIQ,kBAAA,EAGJ,MAAA,4DAPJ,YAIQ,kBAAA,EAGJ,MAAA,2DAPJ,WAIQ,kBAAA,EAGJ,MAAA,0DAPJ,YAIQ,kBAAA,EAGJ,MAAA,2DAPJ,YAIQ,kBAAA,EAGJ,MAAA,2DAPJ,WAIQ,kBAAA,EAGJ,MAAA,0DAPJ,YAIQ,kBAAA,EAGJ,MAAA,kBAPJ,eAIQ,kBAAA,EAGJ,MAAA,yBAPJ,eAIQ,kBAAA,EAGJ,MAAA,+BAPJ,YAIQ,kBAAA,EAGJ,MAAA,kBAjBJ,iBACE,kBAAA,KADF,iBACE,kBAAA,IADF,iBACE,kBAAA,KADF,kBACE,kBAAA,EASF,YAIQ,gBAAA,EAGJ,iBAAA,2DAPJ,cAIQ,gBAAA,EAGJ,iBAAA,6DAPJ,YAIQ,gBAAA,EAGJ,iBAAA,2DAPJ,SAIQ,gBAAA,EAGJ,iBAAA,wDAPJ,YAIQ,gBAAA,EAGJ,iBAAA,2DAPJ,WAIQ,gBAAA,EAGJ,iBAAA,0DAPJ,UAIQ,gBAAA,EAGJ,iBAAA,yDAPJ,SAIQ,gBAAA,EAGJ,iBAAA,wDAPJ,UAIQ,gBAAA,EAGJ,iBAAA,yDAPJ,UAIQ,gBAAA,EAGJ,iBAAA,yDAPJ,SAIQ,gBAAA,EAGJ,iBAAA,wDAPJ,gBAIQ,gBAAA,EAGJ,iBAAA,sBAjBJ,eACE,gBAAA,IADF,eACE,gBAAA,KADF,eACE,gBAAA,IADF,eACE,gBAAA,KADF,gBACE,gBAAA,EASF,aAOI,iBAAA,6BAPJ,iBAOI,oBAAA,cAAA,iBAAA,cAAA,YAAA,cAPJ,kBAOI,oBAAA,eAAA,iBAAA,eAAA,YAAA,eAPJ,kBAOI,oBAAA,eAAA,iBAAA,eAAA,YAAA,eAPJ,SAOI,eAAA,eAPJ,SAOI,eAAA,eAPJ,SAOI,cAAA,iBAPJ,WAOI,cAAA,YAPJ,WAOI,cAAA,gBAPJ,WAOI,cAAA,iBAPJ,WAOI,cAAA,gBAPJ,gBAOI,cAAA,cAPJ,cAOI,cAAA,gBAPJ,aAOI,uBAAA,iBAAA,wBAAA,iBAPJ,aAOI,wBAAA,iBAAA,2BAAA,iBAPJ,gBAOI,2BAAA,iBAAA,0BAAA,iBAPJ,eAOI,0BAAA,iBAAA,uBAAA,iBAPJ,SAOI,WAAA,kBAPJ,WAOI,WAAA,iBzDPR,yByDAI,gBAOI,MAAA,eAPJ,cAOI,MAAA,gBAPJ,eAOI,MAAA,eAPJ,aAOI,QAAA,iBAPJ,mBAOI,QAAA,uBAPJ,YAOI,QAAA,gBAPJ,WAOI,QAAA,eAPJ,YAOI,QAAA,gBAPJ,gBAOI,QAAA,oBAPJ,iBAOI,QAAA,qBAPJ,WAOI,QAAA,eAPJ,kBAOI,QAAA,sBAPJ,WAOI,QAAA,eAPJ,cAOI,KAAA,EAAA,EAAA,eAPJ,aAOI,eAAA,cAPJ,gBAOI,eAAA,iBAPJ,qBAOI,eAAA,sBAPJ,wBAOI,eAAA,yBAPJ,gBAOI,UAAA,YAPJ,gBAOI,UAAA,YAPJ,kBAOI,YAAA,YAPJ,kBAOI,YAAA,YAPJ,cAOI,UAAA,eAPJ,gBAOI,UAAA,iBAPJ,sBAOI,UAAA,uBAPJ,UAOI,IAAA,YAPJ,UAOI,IAAA,iBAPJ,UAOI,IAAA,gBAPJ,UAOI,IAAA,eAPJ,UAOI,IAAA,iBAPJ,UAOI,IAAA,eAPJ,0BAOI,gBAAA,qBAPJ,wBAOI,gBAAA,mBAPJ,2BAOI,gBAAA,iBAPJ,4BAOI,gBAAA,wBAPJ,2BAOI,gBAAA,uBAPJ,2BAOI,gBAAA,uBAPJ,sBAOI,YAAA,qBAPJ,oBAOI,YAAA,mBAPJ,uBAOI,YAAA,iBAPJ,yBAOI,YAAA,mBAPJ,wBAOI,YAAA,kBAPJ,wBAOI,cAAA,qBAPJ,sBAOI,cAAA,mBAPJ,yBAOI,cAAA,iBAPJ,0BAOI,cAAA,wBAPJ,yBAOI,cAAA,uBAPJ,0BAOI,cAAA,kBAPJ,oBAOI,WAAA,eAPJ,qBAOI,WAAA,qBAPJ,mBAOI,WAAA,mBAPJ,sBAOI,WAAA,iBAPJ,wBAOI,WAAA,mBAPJ,uBAOI,WAAA,kBAPJ,gBAOI,MAAA,aAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,eAOI,MAAA,YAPJ,QAOI,OAAA,YAPJ,QAOI,OAAA,iBAPJ,QAOI,OAAA,gBAPJ,QAOI,OAAA,eAPJ,QAOI,OAAA,iBAPJ,QAOI,OAAA,eAPJ,WAOI,OAAA,eAPJ,SAOI,aAAA,YAAA,YAAA,YAPJ,SAOI,aAAA,iBAAA,YAAA,iBAPJ,SAOI,aAAA,gBAAA,YAAA,gBAPJ,SAOI,aAAA,eAAA,YAAA,eAPJ,SAOI,aAAA,iBAAA,YAAA,iBAPJ,SAOI,aAAA,eAAA,YAAA,eAPJ,YAOI,aAAA,eAAA,YAAA,eAPJ,SAOI,WAAA,YAAA,cAAA,YAPJ,SAOI,WAAA,iBAAA,cAAA,iBAPJ,SAOI,WAAA,gBAAA,cAAA,gBAPJ,SAOI,WAAA,eAAA,cAAA,eAPJ,SAOI,WAAA,iBAAA,cAAA,iBAPJ,SAOI,WAAA,eAAA,cAAA,eAPJ,YAOI,WAAA,eAAA,cAAA,eAPJ,SAOI,WAAA,YAPJ,SAOI,WAAA,iBAPJ,SAOI,WAAA,gBAPJ,SAOI,WAAA,eAPJ,SAOI,WAAA,iBAPJ,SAOI,WAAA,eAPJ,YAOI,WAAA,eAPJ,SAOI,aAAA,YAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,gBAPJ,SAOI,aAAA,eAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,eAPJ,YAOI,aAAA,eAPJ,SAOI,cAAA,YAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,gBAPJ,SAOI,cAAA,eAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,eAPJ,YAOI,cAAA,eAPJ,SAOI,YAAA,YAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,gBAPJ,SAOI,YAAA,eAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,eAPJ,YAOI,YAAA,eAPJ,QAOI,QAAA,YAPJ,QAOI,QAAA,iBAPJ,QAOI,QAAA,gBAPJ,QAOI,QAAA,eAPJ,QAOI,QAAA,iBAPJ,QAOI,QAAA,eAPJ,SAOI,cAAA,YAAA,aAAA,YAPJ,SAOI,cAAA,iBAAA,aAAA,iBAPJ,SAOI,cAAA,gBAAA,aAAA,gBAPJ,SAOI,cAAA,eAAA,aAAA,eAPJ,SAOI,cAAA,iBAAA,aAAA,iBAPJ,SAOI,cAAA,eAAA,aAAA,eAPJ,SAOI,YAAA,YAAA,eAAA,YAPJ,SAOI,YAAA,iBAAA,eAAA,iBAPJ,SAOI,YAAA,gBAAA,eAAA,gBAPJ,SAOI,YAAA,eAAA,eAAA,eAPJ,SAOI,YAAA,iBAAA,eAAA,iBAPJ,SAOI,YAAA,eAAA,eAAA,eAPJ,SAOI,YAAA,YAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,gBAPJ,SAOI,YAAA,eAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,eAPJ,SAOI,cAAA,YAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,gBAPJ,SAOI,cAAA,eAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,eAPJ,SAOI,eAAA,YAPJ,SAOI,eAAA,iBAPJ,SAOI,eAAA,gBAPJ,SAOI,eAAA,eAPJ,SAOI,eAAA,iBAPJ,SAOI,eAAA,eAPJ,SAOI,aAAA,YAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,gBAPJ,SAOI,aAAA,eAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,eAPJ,eAOI,WAAA,eAPJ,aAOI,WAAA,gBAPJ,gBAOI,WAAA,kBzDPR,yByDAI,gBAOI,MAAA,eAPJ,cAOI,MAAA,gBAPJ,eAOI,MAAA,eAPJ,aAOI,QAAA,iBAPJ,mBAOI,QAAA,uBAPJ,YAOI,QAAA,gBAPJ,WAOI,QAAA,eAPJ,YAOI,QAAA,gBAPJ,gBAOI,QAAA,oBAPJ,iBAOI,QAAA,qBAPJ,WAOI,QAAA,eAPJ,kBAOI,QAAA,sBAPJ,WAOI,QAAA,eAPJ,cAOI,KAAA,EAAA,EAAA,eAPJ,aAOI,eAAA,cAPJ,gBAOI,eAAA,iBAPJ,qBAOI,eAAA,sBAPJ,wBAOI,eAAA,yBAPJ,gBAOI,UAAA,YAPJ,gBAOI,UAAA,YAPJ,kBAOI,YAAA,YAPJ,kBAOI,YAAA,YAPJ,cAOI,UAAA,eAPJ,gBAOI,UAAA,iBAPJ,sBAOI,UAAA,uBAPJ,UAOI,IAAA,YAPJ,UAOI,IAAA,iBAPJ,UAOI,IAAA,gBAPJ,UAOI,IAAA,eAPJ,UAOI,IAAA,iBAPJ,UAOI,IAAA,eAPJ,0BAOI,gBAAA,qBAPJ,wBAOI,gBAAA,mBAPJ,2BAOI,gBAAA,iBAPJ,4BAOI,gBAAA,wBAPJ,2BAOI,gBAAA,uBAPJ,2BAOI,gBAAA,uBAPJ,sBAOI,YAAA,qBAPJ,oBAOI,YAAA,mBAPJ,uBAOI,YAAA,iBAPJ,yBAOI,YAAA,mBAPJ,wBAOI,YAAA,kBAPJ,wBAOI,cAAA,qBAPJ,sBAOI,cAAA,mBAPJ,yBAOI,cAAA,iBAPJ,0BAOI,cAAA,wBAPJ,yBAOI,cAAA,uBAPJ,0BAOI,cAAA,kBAPJ,oBAOI,WAAA,eAPJ,qBAOI,WAAA,qBAPJ,mBAOI,WAAA,mBAPJ,sBAOI,WAAA,iBAPJ,wBAOI,WAAA,mBAPJ,uBAOI,WAAA,kBAPJ,gBAOI,MAAA,aAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,eAOI,MAAA,YAPJ,QAOI,OAAA,YAPJ,QAOI,OAAA,iBAPJ,QAOI,OAAA,gBAPJ,QAOI,OAAA,eAPJ,QAOI,OAAA,iBAPJ,QAOI,OAAA,eAPJ,WAOI,OAAA,eAPJ,SAOI,aAAA,YAAA,YAAA,YAPJ,SAOI,aAAA,iBAAA,YAAA,iBAPJ,SAOI,aAAA,gBAAA,YAAA,gBAPJ,SAOI,aAAA,eAAA,YAAA,eAPJ,SAOI,aAAA,iBAAA,YAAA,iBAPJ,SAOI,aAAA,eAAA,YAAA,eAPJ,YAOI,aAAA,eAAA,YAAA,eAPJ,SAOI,WAAA,YAAA,cAAA,YAPJ,SAOI,WAAA,iBAAA,cAAA,iBAPJ,SAOI,WAAA,gBAAA,cAAA,gBAPJ,SAOI,WAAA,eAAA,cAAA,eAPJ,SAOI,WAAA,iBAAA,cAAA,iBAPJ,SAOI,WAAA,eAAA,cAAA,eAPJ,YAOI,WAAA,eAAA,cAAA,eAPJ,SAOI,WAAA,YAPJ,SAOI,WAAA,iBAPJ,SAOI,WAAA,gBAPJ,SAOI,WAAA,eAPJ,SAOI,WAAA,iBAPJ,SAOI,WAAA,eAPJ,YAOI,WAAA,eAPJ,SAOI,aAAA,YAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,gBAPJ,SAOI,aAAA,eAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,eAPJ,YAOI,aAAA,eAPJ,SAOI,cAAA,YAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,gBAPJ,SAOI,cAAA,eAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,eAPJ,YAOI,cAAA,eAPJ,SAOI,YAAA,YAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,gBAPJ,SAOI,YAAA,eAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,eAPJ,YAOI,YAAA,eAPJ,QAOI,QAAA,YAPJ,QAOI,QAAA,iBAPJ,QAOI,QAAA,gBAPJ,QAOI,QAAA,eAPJ,QAOI,QAAA,iBAPJ,QAOI,QAAA,eAPJ,SAOI,cAAA,YAAA,aAAA,YAPJ,SAOI,cAAA,iBAAA,aAAA,iBAPJ,SAOI,cAAA,gBAAA,aAAA,gBAPJ,SAOI,cAAA,eAAA,aAAA,eAPJ,SAOI,cAAA,iBAAA,aAAA,iBAPJ,SAOI,cAAA,eAAA,aAAA,eAPJ,SAOI,YAAA,YAAA,eAAA,YAPJ,SAOI,YAAA,iBAAA,eAAA,iBAPJ,SAOI,YAAA,gBAAA,eAAA,gBAPJ,SAOI,YAAA,eAAA,eAAA,eAPJ,SAOI,YAAA,iBAAA,eAAA,iBAPJ,SAOI,YAAA,eAAA,eAAA,eAPJ,SAOI,YAAA,YAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,gBAPJ,SAOI,YAAA,eAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,eAPJ,SAOI,cAAA,YAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,gBAPJ,SAOI,cAAA,eAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,eAPJ,SAOI,eAAA,YAPJ,SAOI,eAAA,iBAPJ,SAOI,eAAA,gBAPJ,SAOI,eAAA,eAPJ,SAOI,eAAA,iBAPJ,SAOI,eAAA,eAPJ,SAOI,aAAA,YAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,gBAPJ,SAOI,aAAA,eAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,eAPJ,eAOI,WAAA,eAPJ,aAOI,WAAA,gBAPJ,gBAOI,WAAA,kBzDPR,yByDAI,gBAOI,MAAA,eAPJ,cAOI,MAAA,gBAPJ,eAOI,MAAA,eAPJ,aAOI,QAAA,iBAPJ,mBAOI,QAAA,uBAPJ,YAOI,QAAA,gBAPJ,WAOI,QAAA,eAPJ,YAOI,QAAA,gBAPJ,gBAOI,QAAA,oBAPJ,iBAOI,QAAA,qBAPJ,WAOI,QAAA,eAPJ,kBAOI,QAAA,sBAPJ,WAOI,QAAA,eAPJ,cAOI,KAAA,EAAA,EAAA,eAPJ,aAOI,eAAA,cAPJ,gBAOI,eAAA,iBAPJ,qBAOI,eAAA,sBAPJ,wBAOI,eAAA,yBAPJ,gBAOI,UAAA,YAPJ,gBAOI,UAAA,YAPJ,kBAOI,YAAA,YAPJ,kBAOI,YAAA,YAPJ,cAOI,UAAA,eAPJ,gBAOI,UAAA,iBAPJ,sBAOI,UAAA,uBAPJ,UAOI,IAAA,YAPJ,UAOI,IAAA,iBAPJ,UAOI,IAAA,gBAPJ,UAOI,IAAA,eAPJ,UAOI,IAAA,iBAPJ,UAOI,IAAA,eAPJ,0BAOI,gBAAA,qBAPJ,wBAOI,gBAAA,mBAPJ,2BAOI,gBAAA,iBAPJ,4BAOI,gBAAA,wBAPJ,2BAOI,gBAAA,uBAPJ,2BAOI,gBAAA,uBAPJ,sBAOI,YAAA,qBAPJ,oBAOI,YAAA,mBAPJ,uBAOI,YAAA,iBAPJ,yBAOI,YAAA,mBAPJ,wBAOI,YAAA,kBAPJ,wBAOI,cAAA,qBAPJ,sBAOI,cAAA,mBAPJ,yBAOI,cAAA,iBAPJ,0BAOI,cAAA,wBAPJ,yBAOI,cAAA,uBAPJ,0BAOI,cAAA,kBAPJ,oBAOI,WAAA,eAPJ,qBAOI,WAAA,qBAPJ,mBAOI,WAAA,mBAPJ,sBAOI,WAAA,iBAPJ,wBAOI,WAAA,mBAPJ,uBAOI,WAAA,kBAPJ,gBAOI,MAAA,aAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,eAOI,MAAA,YAPJ,QAOI,OAAA,YAPJ,QAOI,OAAA,iBAPJ,QAOI,OAAA,gBAPJ,QAOI,OAAA,eAPJ,QAOI,OAAA,iBAPJ,QAOI,OAAA,eAPJ,WAOI,OAAA,eAPJ,SAOI,aAAA,YAAA,YAAA,YAPJ,SAOI,aAAA,iBAAA,YAAA,iBAPJ,SAOI,aAAA,gBAAA,YAAA,gBAPJ,SAOI,aAAA,eAAA,YAAA,eAPJ,SAOI,aAAA,iBAAA,YAAA,iBAPJ,SAOI,aAAA,eAAA,YAAA,eAPJ,YAOI,aAAA,eAAA,YAAA,eAPJ,SAOI,WAAA,YAAA,cAAA,YAPJ,SAOI,WAAA,iBAAA,cAAA,iBAPJ,SAOI,WAAA,gBAAA,cAAA,gBAPJ,SAOI,WAAA,eAAA,cAAA,eAPJ,SAOI,WAAA,iBAAA,cAAA,iBAPJ,SAOI,WAAA,eAAA,cAAA,eAPJ,YAOI,WAAA,eAAA,cAAA,eAPJ,SAOI,WAAA,YAPJ,SAOI,WAAA,iBAPJ,SAOI,WAAA,gBAPJ,SAOI,WAAA,eAPJ,SAOI,WAAA,iBAPJ,SAOI,WAAA,eAPJ,YAOI,WAAA,eAPJ,SAOI,aAAA,YAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,gBAPJ,SAOI,aAAA,eAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,eAPJ,YAOI,aAAA,eAPJ,SAOI,cAAA,YAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,gBAPJ,SAOI,cAAA,eAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,eAPJ,YAOI,cAAA,eAPJ,SAOI,YAAA,YAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,gBAPJ,SAOI,YAAA,eAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,eAPJ,YAOI,YAAA,eAPJ,QAOI,QAAA,YAPJ,QAOI,QAAA,iBAPJ,QAOI,QAAA,gBAPJ,QAOI,QAAA,eAPJ,QAOI,QAAA,iBAPJ,QAOI,QAAA,eAPJ,SAOI,cAAA,YAAA,aAAA,YAPJ,SAOI,cAAA,iBAAA,aAAA,iBAPJ,SAOI,cAAA,gBAAA,aAAA,gBAPJ,SAOI,cAAA,eAAA,aAAA,eAPJ,SAOI,cAAA,iBAAA,aAAA,iBAPJ,SAOI,cAAA,eAAA,aAAA,eAPJ,SAOI,YAAA,YAAA,eAAA,YAPJ,SAOI,YAAA,iBAAA,eAAA,iBAPJ,SAOI,YAAA,gBAAA,eAAA,gBAPJ,SAOI,YAAA,eAAA,eAAA,eAPJ,SAOI,YAAA,iBAAA,eAAA,iBAPJ,SAOI,YAAA,eAAA,eAAA,eAPJ,SAOI,YAAA,YAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,gBAPJ,SAOI,YAAA,eAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,eAPJ,SAOI,cAAA,YAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,gBAPJ,SAOI,cAAA,eAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,eAPJ,SAOI,eAAA,YAPJ,SAOI,eAAA,iBAPJ,SAOI,eAAA,gBAPJ,SAOI,eAAA,eAPJ,SAOI,eAAA,iBAPJ,SAOI,eAAA,eAPJ,SAOI,aAAA,YAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,gBAPJ,SAOI,aAAA,eAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,eAPJ,eAOI,WAAA,eAPJ,aAOI,WAAA,gBAPJ,gBAOI,WAAA,kBzDPR,0ByDAI,gBAOI,MAAA,eAPJ,cAOI,MAAA,gBAPJ,eAOI,MAAA,eAPJ,aAOI,QAAA,iBAPJ,mBAOI,QAAA,uBAPJ,YAOI,QAAA,gBAPJ,WAOI,QAAA,eAPJ,YAOI,QAAA,gBAPJ,gBAOI,QAAA,oBAPJ,iBAOI,QAAA,qBAPJ,WAOI,QAAA,eAPJ,kBAOI,QAAA,sBAPJ,WAOI,QAAA,eAPJ,cAOI,KAAA,EAAA,EAAA,eAPJ,aAOI,eAAA,cAPJ,gBAOI,eAAA,iBAPJ,qBAOI,eAAA,sBAPJ,wBAOI,eAAA,yBAPJ,gBAOI,UAAA,YAPJ,gBAOI,UAAA,YAPJ,kBAOI,YAAA,YAPJ,kBAOI,YAAA,YAPJ,cAOI,UAAA,eAPJ,gBAOI,UAAA,iBAPJ,sBAOI,UAAA,uBAPJ,UAOI,IAAA,YAPJ,UAOI,IAAA,iBAPJ,UAOI,IAAA,gBAPJ,UAOI,IAAA,eAPJ,UAOI,IAAA,iBAPJ,UAOI,IAAA,eAPJ,0BAOI,gBAAA,qBAPJ,wBAOI,gBAAA,mBAPJ,2BAOI,gBAAA,iBAPJ,4BAOI,gBAAA,wBAPJ,2BAOI,gBAAA,uBAPJ,2BAOI,gBAAA,uBAPJ,sBAOI,YAAA,qBAPJ,oBAOI,YAAA,mBAPJ,uBAOI,YAAA,iBAPJ,yBAOI,YAAA,mBAPJ,wBAOI,YAAA,kBAPJ,wBAOI,cAAA,qBAPJ,sBAOI,cAAA,mBAPJ,yBAOI,cAAA,iBAPJ,0BAOI,cAAA,wBAPJ,yBAOI,cAAA,uBAPJ,0BAOI,cAAA,kBAPJ,oBAOI,WAAA,eAPJ,qBAOI,WAAA,qBAPJ,mBAOI,WAAA,mBAPJ,sBAOI,WAAA,iBAPJ,wBAOI,WAAA,mBAPJ,uBAOI,WAAA,kBAPJ,gBAOI,MAAA,aAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,YAOI,MAAA,YAPJ,eAOI,MAAA,YAPJ,QAOI,OAAA,YAPJ,QAOI,OAAA,iBAPJ,QAOI,OAAA,gBAPJ,QAOI,OAAA,eAPJ,QAOI,OAAA,iBAPJ,QAOI,OAAA,eAPJ,WAOI,OAAA,eAPJ,SAOI,aAAA,YAAA,YAAA,YAPJ,SAOI,aAAA,iBAAA,YAAA,iBAPJ,SAOI,aAAA,gBAAA,YAAA,gBAPJ,SAOI,aAAA,eAAA,YAAA,eAPJ,SAOI,aAAA,iBAAA,YAAA,iBAPJ,SAOI,aAAA,eAAA,YAAA,eAPJ,YAOI,aAAA,eAAA,YAAA,eAPJ,SAOI,WAAA,YAAA,cAAA,YAPJ,SAOI,WAAA,iBAAA,cAAA,iBAPJ,SAOI,WAAA,gBAAA,cAAA,gBAPJ,SAOI,WAAA,eAAA,cAAA,eAPJ,SAOI,WAAA,iBAAA,cAAA,iBAPJ,SAOI,WAAA,eAAA,cAAA,eAPJ,YAOI,WAAA,eAAA,cAAA,eAPJ,SAOI,WAAA,YAPJ,SAOI,WAAA,iBAPJ,SAOI,WAAA,gBAPJ,SAOI,WAAA,eAPJ,SAOI,WAAA,iBAPJ,SAOI,WAAA,eAPJ,YAOI,WAAA,eAPJ,SAOI,aAAA,YAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,gBAPJ,SAOI,aAAA,eAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,eAPJ,YAOI,aAAA,eAPJ,SAOI,cAAA,YAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,gBAPJ,SAOI,cAAA,eAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,eAPJ,YAOI,cAAA,eAPJ,SAOI,YAAA,YAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,gBAPJ,SAOI,YAAA,eAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,eAPJ,YAOI,YAAA,eAPJ,QAOI,QAAA,YAPJ,QAOI,QAAA,iBAPJ,QAOI,QAAA,gBAPJ,QAOI,QAAA,eAPJ,QAOI,QAAA,iBAPJ,QAOI,QAAA,eAPJ,SAOI,cAAA,YAAA,aAAA,YAPJ,SAOI,cAAA,iBAAA,aAAA,iBAPJ,SAOI,cAAA,gBAAA,aAAA,gBAPJ,SAOI,cAAA,eAAA,aAAA,eAPJ,SAOI,cAAA,iBAAA,aAAA,iBAPJ,SAOI,cAAA,eAAA,aAAA,eAPJ,SAOI,YAAA,YAAA,eAAA,YAPJ,SAOI,YAAA,iBAAA,eAAA,iBAPJ,SAOI,YAAA,gBAAA,eAAA,gBAPJ,SAOI,YAAA,eAAA,eAAA,eAPJ,SAOI,YAAA,iBAAA,eAAA,iBAPJ,SAOI,YAAA,eAAA,eAAA,eAPJ,SAOI,YAAA,YAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,gBAPJ,SAOI,YAAA,eAPJ,SAOI,YAAA,iBAPJ,SAOI,YAAA,eAPJ,SAOI,cAAA,YAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,gBAPJ,SAOI,cAAA,eAPJ,SAOI,cAAA,iBAPJ,SAOI,cAAA,eAPJ,SAOI,eAAA,YAPJ,SAOI,eAAA,iBAPJ,SAOI,eAAA,gBAPJ,SAOI,eAAA,eAPJ,SAOI,eAAA,iBAPJ,SAOI,eAAA,eAPJ,SAOI,aAAA,YAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,gBAPJ,SAOI,aAAA,eAPJ,SAOI,aAAA,iBAPJ,SAOI,aAAA,eAPJ,eAOI,WAAA,eAPJ,aAOI,WAAA,gBAPJ,gBAOI,WAAA,kBzDPR,0ByDAI,iBAOI,MAAA,eAPJ,eAOI,MAAA,gBAPJ,gBAOI,MAAA,eAPJ,cAOI,QAAA,iBAPJ,oBAOI,QAAA,uBAPJ,aAOI,QAAA,gBAPJ,YAOI,QAAA,eAPJ,aAOI,QAAA,gBAPJ,iBAOI,QAAA,oBAPJ,kBAOI,QAAA,qBAPJ,YAOI,QAAA,eAPJ,mBAOI,QAAA,sBAPJ,YAOI,QAAA,eAPJ,eAOI,KAAA,EAAA,EAAA,eAPJ,cAOI,eAAA,cAPJ,iBAOI,eAAA,iBAPJ,sBAOI,eAAA,sBAPJ,yBAOI,eAAA,yBAPJ,iBAOI,UAAA,YAPJ,iBAOI,UAAA,YAPJ,mBAOI,YAAA,YAPJ,mBAOI,YAAA,YAPJ,eAOI,UAAA,eAPJ,iBAOI,UAAA,iBAPJ,uBAOI,UAAA,uBAPJ,WAOI,IAAA,YAPJ,WAOI,IAAA,iBAPJ,WAOI,IAAA,gBAPJ,WAOI,IAAA,eAPJ,WAOI,IAAA,iBAPJ,WAOI,IAAA,eAPJ,2BAOI,gBAAA,qBAPJ,yBAOI,gBAAA,mBAPJ,4BAOI,gBAAA,iBAPJ,6BAOI,gBAAA,wBAPJ,4BAOI,gBAAA,uBAPJ,4BAOI,gBAAA,uBAPJ,uBAOI,YAAA,qBAPJ,qBAOI,YAAA,mBAPJ,wBAOI,YAAA,iBAPJ,0BAOI,YAAA,mBAPJ,yBAOI,YAAA,kBAPJ,yBAOI,cAAA,qBAPJ,uBAOI,cAAA,mBAPJ,0BAOI,cAAA,iBAPJ,2BAOI,cAAA,wBAPJ,0BAOI,cAAA,uBAPJ,2BAOI,cAAA,kBAPJ,qBAOI,WAAA,eAPJ,sBAOI,WAAA,qBAPJ,oBAOI,WAAA,mBAPJ,uBAOI,WAAA,iBAPJ,yBAOI,WAAA,mBAPJ,wBAOI,WAAA,kBAPJ,iBAOI,MAAA,aAPJ,aAOI,MAAA,YAPJ,aAOI,MAAA,YAPJ,aAOI,MAAA,YAPJ,aAOI,MAAA,YAPJ,aAOI,MAAA,YAPJ,aAOI,MAAA,YAPJ,gBAOI,MAAA,YAPJ,SAOI,OAAA,YAPJ,SAOI,OAAA,iBAPJ,SAOI,OAAA,gBAPJ,SAOI,OAAA,eAPJ,SAOI,OAAA,iBAPJ,SAOI,OAAA,eAPJ,YAOI,OAAA,eAPJ,UAOI,aAAA,YAAA,YAAA,YAPJ,UAOI,aAAA,iBAAA,YAAA,iBAPJ,UAOI,aAAA,gBAAA,YAAA,gBAPJ,UAOI,aAAA,eAAA,YAAA,eAPJ,UAOI,aAAA,iBAAA,YAAA,iBAPJ,UAOI,aAAA,eAAA,YAAA,eAPJ,aAOI,aAAA,eAAA,YAAA,eAPJ,UAOI,WAAA,YAAA,cAAA,YAPJ,UAOI,WAAA,iBAAA,cAAA,iBAPJ,UAOI,WAAA,gBAAA,cAAA,gBAPJ,UAOI,WAAA,eAAA,cAAA,eAPJ,UAOI,WAAA,iBAAA,cAAA,iBAPJ,UAOI,WAAA,eAAA,cAAA,eAPJ,aAOI,WAAA,eAAA,cAAA,eAPJ,UAOI,WAAA,YAPJ,UAOI,WAAA,iBAPJ,UAOI,WAAA,gBAPJ,UAOI,WAAA,eAPJ,UAOI,WAAA,iBAPJ,UAOI,WAAA,eAPJ,aAOI,WAAA,eAPJ,UAOI,aAAA,YAPJ,UAOI,aAAA,iBAPJ,UAOI,aAAA,gBAPJ,UAOI,aAAA,eAPJ,UAOI,aAAA,iBAPJ,UAOI,aAAA,eAPJ,aAOI,aAAA,eAPJ,UAOI,cAAA,YAPJ,UAOI,cAAA,iBAPJ,UAOI,cAAA,gBAPJ,UAOI,cAAA,eAPJ,UAOI,cAAA,iBAPJ,UAOI,cAAA,eAPJ,aAOI,cAAA,eAPJ,UAOI,YAAA,YAPJ,UAOI,YAAA,iBAPJ,UAOI,YAAA,gBAPJ,UAOI,YAAA,eAPJ,UAOI,YAAA,iBAPJ,UAOI,YAAA,eAPJ,aAOI,YAAA,eAPJ,SAOI,QAAA,YAPJ,SAOI,QAAA,iBAPJ,SAOI,QAAA,gBAPJ,SAOI,QAAA,eAPJ,SAOI,QAAA,iBAPJ,SAOI,QAAA,eAPJ,UAOI,cAAA,YAAA,aAAA,YAPJ,UAOI,cAAA,iBAAA,aAAA,iBAPJ,UAOI,cAAA,gBAAA,aAAA,gBAPJ,UAOI,cAAA,eAAA,aAAA,eAPJ,UAOI,cAAA,iBAAA,aAAA,iBAPJ,UAOI,cAAA,eAAA,aAAA,eAPJ,UAOI,YAAA,YAAA,eAAA,YAPJ,UAOI,YAAA,iBAAA,eAAA,iBAPJ,UAOI,YAAA,gBAAA,eAAA,gBAPJ,UAOI,YAAA,eAAA,eAAA,eAPJ,UAOI,YAAA,iBAAA,eAAA,iBAPJ,UAOI,YAAA,eAAA,eAAA,eAPJ,UAOI,YAAA,YAPJ,UAOI,YAAA,iBAPJ,UAOI,YAAA,gBAPJ,UAOI,YAAA,eAPJ,UAOI,YAAA,iBAPJ,UAOI,YAAA,eAPJ,UAOI,cAAA,YAPJ,UAOI,cAAA,iBAPJ,UAOI,cAAA,gBAPJ,UAOI,cAAA,eAPJ,UAOI,cAAA,iBAPJ,UAOI,cAAA,eAPJ,UAOI,eAAA,YAPJ,UAOI,eAAA,iBAPJ,UAOI,eAAA,gBAPJ,UAOI,eAAA,eAPJ,UAOI,eAAA,iBAPJ,UAOI,eAAA,eAPJ,UAOI,aAAA,YAPJ,UAOI,aAAA,iBAPJ,UAOI,aAAA,gBAPJ,UAOI,aAAA,eAPJ,UAOI,aAAA,iBAPJ,UAOI,aAAA,eAPJ,gBAOI,WAAA,eAPJ,cAOI,WAAA,gBAPJ,iBAOI,WAAA,kBCnDZ,0BD4CQ,MAOI,UAAA,iBAPJ,MAOI,UAAA,eAPJ,MAOI,UAAA,kBAPJ,MAOI,UAAA,kBChCZ,aDyBQ,gBAOI,QAAA,iBAPJ,sBAOI,QAAA,uBAPJ,eAOI,QAAA,gBAPJ,cAOI,QAAA,eAPJ,eAOI,QAAA,gBAPJ,mBAOI,QAAA,oBAPJ,oBAOI,QAAA,qBAPJ,cAOI,QAAA,eAPJ,qBAOI,QAAA,sBAPJ,cAOI,QAAA","sourcesContent":["/*!\n * Bootstrap v5.1.0 (https://getbootstrap.com/)\n * Copyright 2011-2021 The Bootstrap Authors\n * Copyright 2011-2021 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n// scss-docs-start import-stack\n// Configuration\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"utilities\";\n\n// Layout & components\n@import \"root\";\n@import \"reboot\";\n@import \"type\";\n@import \"images\";\n@import \"containers\";\n@import \"grid\";\n@import \"tables\";\n@import \"forms\";\n@import \"buttons\";\n@import \"transitions\";\n@import \"dropdown\";\n@import \"button-group\";\n@import \"nav\";\n@import \"navbar\";\n@import \"card\";\n@import \"accordion\";\n@import \"breadcrumb\";\n@import \"pagination\";\n@import \"badge\";\n@import \"alert\";\n@import \"progress\";\n@import \"list-group\";\n@import \"close\";\n@import \"toasts\";\n@import \"modal\";\n@import \"tooltip\";\n@import \"popover\";\n@import \"carousel\";\n@import \"spinners\";\n@import \"offcanvas\";\n@import \"placeholders\";\n\n// Helpers\n@import \"helpers\";\n\n// Utilities\n@import \"utilities/api\";\n// scss-docs-end import-stack\n",":root {\n // Note: Custom variable values only support SassScript inside `#{}`.\n\n // Colors\n //\n // Generate palettes for full colors, grays, and theme colors.\n\n @each $color, $value in $colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $grays {\n --#{$variable-prefix}gray-#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors-rgb {\n --#{$variable-prefix}#{$color}-rgb: #{$value};\n }\n\n --#{$variable-prefix}white-rgb: #{to-rgb($white)};\n --#{$variable-prefix}black-rgb: #{to-rgb($black)};\n --#{$variable-prefix}body-rgb: #{to-rgb($body-color)};\n\n // Fonts\n\n // Note: Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --#{$variable-prefix}font-sans-serif: #{inspect($font-family-sans-serif)};\n --#{$variable-prefix}font-monospace: #{inspect($font-family-monospace)};\n --#{$variable-prefix}gradient: #{$gradient};\n\n // Root and body\n // stylelint-disable custom-property-empty-line-before\n // scss-docs-start root-body-variables\n @if $font-size-root != null {\n --#{$variable-prefix}root-font-size: #{$font-size-root};\n }\n --#{$variable-prefix}body-font-family: #{$font-family-base};\n --#{$variable-prefix}body-font-size: #{$font-size-base};\n --#{$variable-prefix}body-font-weight: #{$font-weight-base};\n --#{$variable-prefix}body-line-height: #{$line-height-base};\n --#{$variable-prefix}body-color: #{$body-color};\n @if $body-text-align != null {\n --#{$variable-prefix}body-text-align: #{$body-text-align};\n }\n --#{$variable-prefix}body-bg: #{$body-bg};\n // scss-docs-end root-body-variables\n // stylelint-enable custom-property-empty-line-before\n}\n","// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n\n// Root\n//\n// Ability to the value of the root font sizes, affecting the value of `rem`.\n// null by default, thus nothing is generated.\n\n:root {\n @if $font-size-root != null {\n font-size: var(--#{$variable-prefix}-root-font-size);\n }\n\n @if $enable-smooth-scroll {\n @media (prefers-reduced-motion: no-preference) {\n scroll-behavior: smooth;\n }\n }\n}\n\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Prevent adjustments of font size after orientation changes in iOS.\n// 4. Change the default tap highlight to be completely transparent in iOS.\n\n// scss-docs-start reboot-body-rules\nbody {\n margin: 0; // 1\n font-family: var(--#{$variable-prefix}body-font-family);\n @include font-size(var(--#{$variable-prefix}body-font-size));\n font-weight: var(--#{$variable-prefix}body-font-weight);\n line-height: var(--#{$variable-prefix}body-line-height);\n color: var(--#{$variable-prefix}body-color);\n text-align: var(--#{$variable-prefix}body-text-align);\n background-color: var(--#{$variable-prefix}body-bg); // 2\n -webkit-text-size-adjust: 100%; // 3\n -webkit-tap-highlight-color: rgba($black, 0); // 4\n}\n// scss-docs-end reboot-body-rules\n\n\n// Content grouping\n//\n// 1. Reset Firefox's gray color\n// 2. Set correct height and prevent the `size` attribute to make the `hr` look like an input field\n\nhr {\n margin: $hr-margin-y 0;\n color: $hr-color; // 1\n background-color: currentColor;\n border: 0;\n opacity: $hr-opacity;\n}\n\nhr:not([size]) {\n height: $hr-height; // 2\n}\n\n\n// Typography\n//\n// 1. Remove top margins from headings\n// By default, `

    `-`

    ` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n\n%heading {\n margin-top: 0; // 1\n margin-bottom: $headings-margin-bottom;\n font-family: $headings-font-family;\n font-style: $headings-font-style;\n font-weight: $headings-font-weight;\n line-height: $headings-line-height;\n color: $headings-color;\n}\n\nh1 {\n @extend %heading;\n @include font-size($h1-font-size);\n}\n\nh2 {\n @extend %heading;\n @include font-size($h2-font-size);\n}\n\nh3 {\n @extend %heading;\n @include font-size($h3-font-size);\n}\n\nh4 {\n @extend %heading;\n @include font-size($h4-font-size);\n}\n\nh5 {\n @extend %heading;\n @include font-size($h5-font-size);\n}\n\nh6 {\n @extend %heading;\n @include font-size($h6-font-size);\n}\n\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

    `s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\n\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n\n// Abbreviations\n//\n// 1. Duplicate behavior to the data-bs-* attribute for our tooltip plugin\n// 2. Add the correct text decoration in Chrome, Edge, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Prevent the text-decoration to be skipped.\n\nabbr[title],\nabbr[data-bs-original-title] { // 1\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n text-decoration-skip-ink: none; // 4\n}\n\n\n// Address\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\n\n// Lists\n\nol,\nul {\n padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\n// 1. Undo browser default\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // 1\n}\n\n\n// Blockquote\n\nblockquote {\n margin: 0 0 1rem;\n}\n\n\n// Strong\n//\n// Add the correct font weight in Chrome, Edge, and Safari\n\nb,\nstrong {\n font-weight: $font-weight-bolder;\n}\n\n\n// Small\n//\n// Add the correct font size in all browsers\n\nsmall {\n @include font-size($small-font-size);\n}\n\n\n// Mark\n\nmark {\n padding: $mark-padding;\n background-color: $mark-bg;\n}\n\n\n// Sub and Sup\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n\nsub,\nsup {\n position: relative;\n @include font-size($sub-sup-font-size);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n// Links\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n\n &:hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([class]) {\n &,\n &:hover {\n color: inherit;\n text-decoration: none;\n }\n}\n\n\n// Code\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-code;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n direction: ltr #{\"/* rtl:ignore */\"};\n unicode-bidi: bidi-override;\n}\n\n// 1. Remove browser default top margin\n// 2. Reset browser default of `1em` to use `rem`s\n// 3. Don't allow content to break outside\n\npre {\n display: block;\n margin-top: 0; // 1\n margin-bottom: 1rem; // 2\n overflow: auto; // 3\n @include font-size($code-font-size);\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n @include font-size(inherit);\n color: inherit;\n word-break: normal;\n }\n}\n\ncode {\n @include font-size($code-font-size);\n color: $code-color;\n word-wrap: break-word;\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n color: inherit;\n }\n}\n\nkbd {\n padding: $kbd-padding-y $kbd-padding-x;\n @include font-size($kbd-font-size);\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n\n kbd {\n padding: 0;\n @include font-size(1em);\n font-weight: $nested-kbd-font-weight;\n }\n}\n\n\n// Figures\n//\n// Apply a consistent margin strategy (matches our type styles).\n\nfigure {\n margin: 0 0 1rem;\n}\n\n\n// Images and content\n\nimg,\nsvg {\n vertical-align: middle;\n}\n\n\n// Tables\n//\n// Prevent double borders\n\ntable {\n caption-side: bottom;\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: $table-cell-padding-y;\n padding-bottom: $table-cell-padding-y;\n color: $table-caption-color;\n text-align: left;\n}\n\n// 1. Removes font-weight bold by inheriting\n// 2. Matches default `` alignment by inheriting `text-align`.\n// 3. Fix alignment for Safari\n\nth {\n font-weight: $table-th-font-weight; // 1\n text-align: inherit; // 2\n text-align: -webkit-match-parent; // 3\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n border-color: inherit;\n border-style: solid;\n border-width: 0;\n}\n\n\n// Forms\n//\n// 1. Allow labels to use `margin` for spacing.\n\nlabel {\n display: inline-block; // 1\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n// See https://github.com/twbs/bootstrap/issues/24093\n\nbutton {\n // stylelint-disable-next-line property-disallowed-list\n border-radius: 0;\n}\n\n// Explicitly remove focus outline in Chromium when it shouldn't be\n// visible (e.g. as result of mouse click or touch tap). It already\n// should be doing this automatically, but seems to currently be\n// confused and applies its very visible two-tone outline anyway.\n\nbutton:focus:not(:focus-visible) {\n outline: 0;\n}\n\n// 1. Remove the margin in Firefox and Safari\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // 1\n font-family: inherit;\n @include font-size(inherit);\n line-height: inherit;\n}\n\n// Remove the inheritance of text transform in Firefox\nbutton,\nselect {\n text-transform: none;\n}\n// Set the cursor for non-` - + +

    Page @(State.CurrentPageIndex + 1) of @(State.LastPageIndex + 1)
    - - + + } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Samples/BlazorUnitedApp/Program.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Samples/BlazorUnitedApp/Program.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Samples/BlazorUnitedApp/Program.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Samples/BlazorUnitedApp/Program.cs 2023-11-13 13:20:34.000000000 +0000 @@ -24,6 +24,7 @@ app.UseHttpsRedirection(); app.UseStaticFiles(); +app.UseAntiforgery(); app.MapRazorComponents(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/src/Circuits/CircuitHost.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/src/Circuits/CircuitHost.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/src/Circuits/CircuitHost.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/src/Circuits/CircuitHost.cs 2023-11-13 13:20:34.000000000 +0000 @@ -726,7 +726,7 @@ } internal Task UpdateRootComponents( - (RootComponentOperation, ComponentDescriptor?)[] operations, + RootComponentOperationBatch operationBatch, ProtectedPrerenderComponentApplicationStore store, IServerComponentDeserializer serverComponentDeserializer, CancellationToken cancellation) @@ -735,7 +735,10 @@ return Renderer.Dispatcher.InvokeAsync(async () => { + var webRootComponentManager = Renderer.GetOrCreateWebRootComponentManager(); var shouldClearStore = false; + var operations = operationBatch.Operations; + var batchId = operationBatch.BatchId; Task[]? pendingTasks = null; try { @@ -766,7 +769,7 @@ for (var i = 0; i < operations.Length; i++) { var operation = operations[i]; - if (operation.Item1.Type != RootComponentOperationType.Add) + if (operation.Type != RootComponentOperationType.Add) { throw new InvalidOperationException($"The first set of update operations must always be of type {nameof(RootComponentOperationType.Add)}"); } @@ -775,32 +778,32 @@ pendingTasks = new Task[operations.Length]; } - for (var i = 0; i < operations.Length;i++) + for (var i = 0; i < operations.Length; i++) { - var (operation, descriptor) = operations[i]; + var operation = operations[i]; switch (operation.Type) { case RootComponentOperationType.Add: - var task = Renderer.AddComponentAsync(descriptor.ComponentType, descriptor.Parameters, operation.SelectorId.Value.ToString(CultureInfo.InvariantCulture)); + var task = webRootComponentManager.AddRootComponentAsync( + operation.SsrComponentId, + operation.Descriptor.ComponentType, + operation.Marker.Value.Key, + operation.Descriptor.Parameters); if (pendingTasks != null) { pendingTasks[i] = task; } break; case RootComponentOperationType.Update: - var componentType = Renderer.GetExistingComponentType(operation.ComponentId.Value); - if (descriptor.ComponentType != componentType) - { - Log.InvalidComponentTypeForUpdate(_logger, message: "Component type mismatch."); - throw new InvalidOperationException($"Incorrect type for descriptor '{descriptor.ComponentType.FullName}'"); - } - // We don't need to await component updates as any unhandled exception will be reported and terminate the circuit. - _ = Renderer.UpdateRootComponentAsync(operation.ComponentId.Value, descriptor.Parameters); - + _ = webRootComponentManager.UpdateRootComponentAsync( + operation.SsrComponentId, + operation.Descriptor.ComponentType, + operation.Marker.Value.Key, + operation.Descriptor.Parameters); break; case RootComponentOperationType.Remove: - Renderer.RemoveExistingRootComponent(operation.ComponentId.Value); + webRootComponentManager.RemoveRootComponent(operation.SsrComponentId); break; } } @@ -810,6 +813,8 @@ await Task.WhenAll(pendingTasks); } + await Client.SendAsync("JS.EndUpdateRootComponents", batchId); + Log.UpdateRootComponentsSucceeded(_logger); } catch (Exception ex) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/src/Circuits/IServerComponentDeserializer.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/src/Circuits/IServerComponentDeserializer.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/src/Circuits/IServerComponentDeserializer.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/src/Circuits/IServerComponentDeserializer.cs 2023-11-13 13:20:34.000000000 +0000 @@ -10,6 +10,5 @@ bool TryDeserializeComponentDescriptorCollection( string serializedComponentRecords, out List descriptors); - bool TryDeserializeSingleComponentDescriptor(ComponentMarker record, [NotNullWhen(true)] out ComponentDescriptor? result); - bool TryDeserializeRootComponentOperations(string serializedComponentOperations, out (RootComponentOperation, ComponentDescriptor?)[] operationsWithDescriptors); + bool TryDeserializeRootComponentOperations(string serializedComponentOperations, [NotNullWhen(true)] out RootComponentOperationBatch? operationBatch); } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/src/Circuits/RemoteNavigationInterception.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/src/Circuits/RemoteNavigationInterception.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/src/Circuits/RemoteNavigationInterception.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/src/Circuits/RemoteNavigationInterception.cs 2023-11-13 13:20:34.000000000 +0000 @@ -35,6 +35,6 @@ "attempted during prerendering or while the client is disconnected."); } - await _jsRuntime.InvokeAsync(Interop.EnableNavigationInterception); + await _jsRuntime.InvokeAsync(Interop.EnableNavigationInterception, WebRendererId.Server); } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/src/Circuits/RemoteNavigationManager.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/src/Circuits/RemoteNavigationManager.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/src/Circuits/RemoteNavigationManager.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/src/Circuits/RemoteNavigationManager.cs 2023-11-13 13:20:34.000000000 +0000 @@ -157,7 +157,7 @@ { try { - await _jsRuntime.InvokeVoidAsync(Interop.SetHasLocationChangingListeners, value); + await _jsRuntime.InvokeVoidAsync(Interop.SetHasLocationChangingListeners, WebRendererId.Server, value); } catch (JSDisconnectedException) { diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/src/Circuits/RemoteRenderer.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/src/Circuits/RemoteRenderer.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/src/Circuits/RemoteRenderer.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/src/Circuits/RemoteRenderer.cs 2023-11-13 13:20:34.000000000 +0000 @@ -70,12 +70,6 @@ _ = CaptureAsyncExceptions(attachComponentTask); } - internal Task UpdateRootComponentAsync(int componentId, ParameterView initialParameters) => - RenderRootComponentAsync(componentId, initialParameters); - - internal void RemoveExistingRootComponent(int componentId) => - RemoveRootComponent(componentId); - internal Type GetExistingComponentType(int componentId) => GetComponentState(componentId).Component.GetType(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/src/Circuits/ServerComponentDeserializer.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/src/Circuits/ServerComponentDeserializer.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/src/Circuits/ServerComponentDeserializer.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/src/Circuits/ServerComponentDeserializer.cs 2023-11-13 13:20:34.000000000 +0000 @@ -100,14 +100,7 @@ var previousInstance = new ServerComponent(); foreach (var marker in markers) { - if (!IsWellFormedServerComponent(marker)) - { - // The client sent us a marker with missing or obviously incorrect info. - descriptors.Clear(); - return false; - } - - var (descriptor, serverComponent) = DeserializeServerComponent(marker); + var (descriptor, serverComponent) = DeserializeComponentDescriptor(marker); if (descriptor == null) { // We failed to deserialize the component descriptor for some reason. @@ -149,20 +142,18 @@ return true; } - public bool TryDeserializeSingleComponentDescriptor(ComponentMarker record, [NotNullWhen(true)] out ComponentDescriptor? result) + public bool TryDeserializeWebRootComponentDescriptor(ComponentMarker record, [NotNullWhen(true)] out WebRootComponentDescriptor? result) { result = default; - if (!IsWellFormedServerComponent(record)) + if (!TryDeserializeServerComponent(record, out var serverComponent)) { - // The client sent us a marker with missing or obviously incorrect info. return false; } - var (descriptor, serverComponent) = DeserializeServerComponent(record); - if (descriptor is null) + if (record.Key != serverComponent.Key) { - // We failed to deserialize the component descriptor for some reason. + Log.InvalidRootComponentKey(_logger); return false; } @@ -196,13 +187,47 @@ return false; } + if (!TryDeserializeComponentTypeAndParameters(serverComponent, out var componentType, out var parameters)) + { + return false; + } + _seenSequenceNumbersForCurrentInvocation.Add(serverComponent.Sequence); - result = descriptor; + + var webRootComponentParameters = new WebRootComponentParameters( + parameters, + serverComponent.ParameterDefinitions.AsReadOnly(), + serverComponent.ParameterValues.AsReadOnly()); + + result = new(componentType, webRootComponentParameters); + return true; + } + + private bool TryDeserializeComponentTypeAndParameters(ServerComponent serverComponent, [NotNullWhen(true)] out Type? componentType, out ParameterView parameters) + { + parameters = default; + componentType = _rootComponentTypeCache + .GetRootComponent(serverComponent.AssemblyName, serverComponent.TypeName); + + if (componentType == null) + { + Log.FailedToFindComponent(_logger, serverComponent.TypeName, serverComponent.AssemblyName); + return false; + } + + if (!_parametersDeserializer.TryDeserializeParameters(serverComponent.ParameterDefinitions, serverComponent.ParameterValues, out parameters)) + { + // TryDeserializeParameters does appropriate logging. + return default; + } + return true; } - private bool IsWellFormedServerComponent(ComponentMarker record) + private bool TryDeserializeServerComponent(ComponentMarker record, out ServerComponent result) { + result = default; + if (record.Type is not (ComponentMarker.ServerMarkerType or ComponentMarker.AutoMarkerType)) { Log.InvalidMarkerType(_logger, record.Type); @@ -215,11 +240,6 @@ return false; } - return true; - } - - private (ComponentDescriptor, ServerComponent) DeserializeServerComponent(ComponentMarker record) - { string unprotected; try { @@ -230,34 +250,34 @@ catch (Exception e) { Log.FailedToUnprotectDescriptor(_logger, e); - return default; + result = default; + return false; } - ServerComponent serverComponent; try { - serverComponent = JsonSerializer.Deserialize( + result = JsonSerializer.Deserialize( unprotected, ServerComponentSerializationSettings.JsonSerializationOptions); + return true; } catch (Exception e) { Log.FailedToDeserializeDescriptor(_logger, e); - return default; + result = default; + return false; } + } - var componentType = _rootComponentTypeCache - .GetRootComponent(serverComponent.AssemblyName, serverComponent.TypeName); - - if (componentType == null) + private (ComponentDescriptor, ServerComponent) DeserializeComponentDescriptor(ComponentMarker record) + { + if (!TryDeserializeServerComponent(record, out var serverComponent)) { - Log.FailedToFindComponent(_logger, serverComponent.TypeName, serverComponent.AssemblyName); return default; } - if (!_parametersDeserializer.TryDeserializeParameters(serverComponent.ParameterDefinitions, serverComponent.ParameterValues, out var parameters)) + if (!TryDeserializeComponentTypeAndParameters(serverComponent, out var componentType, out var parameters)) { - // TryDeserializeParameters does appropriate logging. return default; } @@ -265,90 +285,65 @@ { ComponentType = componentType, Parameters = parameters, - Sequence = serverComponent.Sequence + Sequence = serverComponent.Sequence, }; return (componentDescriptor, serverComponent); } - public bool TryDeserializeRootComponentOperations(string serializedComponentOperations, out (RootComponentOperation, ComponentDescriptor?)[] operations) + public bool TryDeserializeRootComponentOperations(string serializedComponentOperations, [NotNullWhen(true)] out RootComponentOperationBatch? result) { int[]? seenComponentIdsStorage = null; try { - var result = JsonSerializer.Deserialize( + result = JsonSerializer.Deserialize( serializedComponentOperations, ServerComponentSerializationSettings.JsonSerializationOptions); + var operations = result.Operations; - operations = new (RootComponentOperation, ComponentDescriptor?)[result.Length]; - - Span seenComponentIds = result.Length <= 128 - ? stackalloc int[result.Length] - : (seenComponentIdsStorage = ArrayPool.Shared.Rent(result.Length)).AsSpan(0, result.Length); - var currentComponentIdIndex = 0; - for (var i = 0; i < result.Length; i++) + Span seenSsrComponentIds = operations.Length <= 128 + ? stackalloc int[operations.Length] + : (seenComponentIdsStorage = ArrayPool.Shared.Rent(operations.Length)).AsSpan(0, operations.Length); + var currentSsrComponentIdIndex = 0; + for (var i = 0; i < operations.Length; i++) { - var operation = result[i]; - if (operation.Type == RootComponentOperationType.Remove || - operation.Type == RootComponentOperationType.Update) + var operation = operations[i]; + if (seenSsrComponentIds[0..currentSsrComponentIdIndex].Contains(operation.SsrComponentId)) { - if (operation.ComponentId == null) - { - Log.InvalidRootComponentOperation(_logger, operation.Type, message: "Missing component ID."); - operations = null; - return false; - } - - if (seenComponentIds[0..currentComponentIdIndex] - .Contains(operation.ComponentId.Value)) - { - Log.InvalidRootComponentOperation(_logger, operation.Type, message: "Duplicate component ID."); - operations = null; - return false; - } - - seenComponentIds[currentComponentIdIndex++] = operation.ComponentId.Value; + Log.InvalidRootComponentOperation(_logger, operation.Type, message: "Duplicate component ID."); + result = null; + return false; } + seenSsrComponentIds[currentSsrComponentIdIndex++] = operation.SsrComponentId; + if (operation.Type == RootComponentOperationType.Remove) { - operations[i] = (operation, null); continue; } - if (operation.Type == RootComponentOperationType.Add) - { - if (operation.SelectorId is not { } selectorId) - { - Log.InvalidRootComponentOperation(_logger, operation.Type, message: "Missing selector ID."); - operations = null; - return false; - } - } - if (operation.Marker == null) { Log.InvalidRootComponentOperation(_logger, operation.Type, message: "Missing marker."); - operations = null; + result = null; return false; } - if (!TryDeserializeSingleComponentDescriptor(operation.Marker.Value, out var descriptor)) + if (!TryDeserializeWebRootComponentDescriptor(operation.Marker.Value, out var descriptor)) { - operations = null; + result = null; return false; } - operations[i] = (operation, descriptor); + operation.Descriptor = descriptor; } return true; - } catch (Exception ex) { Log.FailedToProcessRootComponentOperations(_logger, ex); - operations = null; + result = null; return false; } finally @@ -397,5 +392,8 @@ [LoggerMessage(12, LogLevel.Debug, "Failed to parse root component operations", EventName = nameof(FailedToProcessRootComponentOperations))] public static partial void FailedToProcessRootComponentOperations(ILogger logger, Exception exception); + + [LoggerMessage(13, LogLevel.Debug, "The provided root component key was not valid.", EventName = nameof(InvalidRootComponentKey))] + public static partial void InvalidRootComponentKey(ILogger logger); } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -57,9 +57,12 @@ + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/test/Circuits/CircuitHostTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/test/Circuits/CircuitHostTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/test/Circuits/CircuitHostTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/test/Circuits/CircuitHostTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -1,7 +1,7 @@ // 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.CodeAnalysis; +using System.Globalization; using System.Reflection; using System.Text.Json; using Microsoft.AspNetCore.Components.Endpoints; @@ -423,22 +423,9 @@ { [nameof(DynamicallyAddedComponent.Message)] = expectedMessage, }; - var operation = new RootComponentOperation - { - Type = RootComponentOperationType.Add, - SelectorId = 1, - Marker = CreateMarker(typeof(DynamicallyAddedComponent), parameters), - }; - var descriptor = new ComponentDescriptor() - { - ComponentType = typeof(DynamicallyAddedComponent), - Parameters = ParameterView.FromDictionary(parameters), - Sequence = 0, - }; // Act - await circuitHost.UpdateRootComponents( - [(operation, descriptor)], null, CreateDeserializer(), CancellationToken.None); + await AddComponentAsync(circuitHost, 1, parameters); // Assert var componentState = ((TestRemoteRenderer)circuitHost.Renderer).GetTestComponentState(0); @@ -453,35 +440,48 @@ var circuitHost = TestCircuitHost.Create( remoteRenderer: GetRemoteRenderer(), serviceScope: new ServiceCollection().BuildServiceProvider().CreateAsyncScope()); - var expectedMessage = "Updated message"; + var componentKey = "mykey"; + + await AddComponentAsync(circuitHost, 1, null, componentKey); + var expectedMessage = "Updated message"; Dictionary parameters = new() { [nameof(DynamicallyAddedComponent.Message)] = expectedMessage, }; - await AddComponent(circuitHost, parameters); - var operation = new RootComponentOperation - { - Type = RootComponentOperationType.Update, - ComponentId = 0, - Marker = CreateMarker(typeof(DynamicallyAddedComponent), new() - { - [nameof(DynamicallyAddedComponent.Message)] = expectedMessage, - }), - }; - var descriptor = new ComponentDescriptor() + // Act + await UpdateComponentAsync(circuitHost, 1, parameters, componentKey); + + // Assert + var componentState = ((TestRemoteRenderer)circuitHost.Renderer).GetTestComponentState(0); + var component = Assert.IsType(componentState.Component); + Assert.Equal(expectedMessage, component.Message); + } + + [Fact] + public async Task UpdateRootComponents_CanReplaceExistingRootComponent_WhenNoComponentKeyWasSpecified() + { + // Arrange + var circuitHost = TestCircuitHost.Create( + remoteRenderer: GetRemoteRenderer(), + serviceScope: new ServiceCollection().BuildServiceProvider().CreateAsyncScope()); + + await AddComponentAsync(circuitHost, 1); + + var expectedMessage = "Updated message"; + Dictionary parameters = new() { - ComponentType = typeof(DynamicallyAddedComponent), - Parameters = ParameterView.FromDictionary(new Dictionary()), - Sequence = 0, + [nameof(DynamicallyAddedComponent.Message)] = expectedMessage, }; // Act - await circuitHost.UpdateRootComponents([(operation, descriptor)], null, CreateDeserializer(), CancellationToken.None); + await UpdateComponentAsync(circuitHost, 1, parameters); // Assert - var componentState = ((TestRemoteRenderer)circuitHost.Renderer).GetTestComponentState(0); + Assert.Throws(() => + ((TestRemoteRenderer)circuitHost.Renderer).GetTestComponentState(0)); + var componentState = ((TestRemoteRenderer)circuitHost.Renderer).GetTestComponentState(1); var component = Assert.IsType(componentState.Component); Assert.Equal(expectedMessage, component.Message); } @@ -496,39 +496,60 @@ // Arrange var expectedMessage = "Existing message"; - await AddComponent(circuitHost, new Dictionary() + await AddComponentAsync(circuitHost, 1, new Dictionary() { [nameof(DynamicallyAddedComponent.Message)] = expectedMessage, }); - await AddComponent(circuitHost, []); + await AddComponentAsync(circuitHost, 2, []); Dictionary parameters = new() { [nameof(DynamicallyAddedComponent.Message)] = "Updated message", }; - var operation = new RootComponentOperation + + // Act + var evt = await Assert.RaisesAsync( + handler => circuitHost.UnhandledException += new UnhandledExceptionEventHandler(handler), + handler => circuitHost.UnhandledException -= new UnhandledExceptionEventHandler(handler), + () => UpdateComponentAsync(circuitHost, 1, parameters)); + + // Assert + var componentState = ((TestRemoteRenderer)circuitHost.Renderer).GetTestComponentState(0); + var component = Assert.IsType(componentState.Component); + Assert.Equal(expectedMessage, component.Message); + + Assert.NotNull(evt); + var exception = Assert.IsType(evt.Arguments.ExceptionObject); + Assert.Equal("Cannot update components with mismatching types.", exception.Message); + } + + [Fact] + public async Task UpdateRootComponents_DoesNotUpdateExistingRootComponent_WhenDescriptorKeyDoesNotMatchOriginalKey() + { + // Arrange + var circuitHost = TestCircuitHost.Create( + remoteRenderer: GetRemoteRenderer(), + serviceScope: new ServiceCollection().BuildServiceProvider().CreateAsyncScope()); + + // Arrange + var originalKey = "original_key"; + var expectedMessage = "Existing message"; + await AddComponentAsync(circuitHost, 1, new Dictionary() { - Type = RootComponentOperationType.Update, - ComponentId = 0, - Marker = CreateMarker(typeof(TestComponent) /* Note the incorrect component type */, parameters), - }; - var descriptor = new ComponentDescriptor() + [nameof(DynamicallyAddedComponent.Message)] = expectedMessage, + }, originalKey); + + Dictionary parameters = new() { - ComponentType = typeof(TestComponent), - Parameters = ParameterView.FromDictionary(parameters), - Sequence = 0, + [nameof(DynamicallyAddedComponent.Message)] = "Updated message", }; - var operationsJson = JsonSerializer.Serialize( - new[] { operation }, - ServerComponentSerializationSettings.JsonSerializationOptions); // Act - var evt = Assert.Raises( + var evt = await Assert.RaisesAsync( handler => circuitHost.UnhandledException += new UnhandledExceptionEventHandler(handler), handler => circuitHost.UnhandledException -= new UnhandledExceptionEventHandler(handler), - () => circuitHost.UpdateRootComponents( - [(operation, descriptor)], null, CreateDeserializer(), CancellationToken.None)); + () => UpdateComponentAsync(circuitHost, 1, parameters, componentKey: "new_key")); // Assert var componentState = ((TestRemoteRenderer)circuitHost.Renderer).GetTestComponentState(0); @@ -537,6 +558,7 @@ Assert.NotNull(evt); var exception = Assert.IsType(evt.Arguments.ExceptionObject); + Assert.Equal("Cannot update components with mismatching keys.", exception.Message); } [Fact] @@ -552,41 +574,62 @@ { [nameof(DynamicallyAddedComponent.Message)] = expectedMessage, }; - await AddComponent(circuitHost, parameters); - - var operation = new RootComponentOperation - { - Type = RootComponentOperationType.Remove, - ComponentId = 0, - }; + await AddComponentAsync(circuitHost, 1, parameters); // Act - await circuitHost.UpdateRootComponents([(operation, null)], null, CreateDeserializer(), CancellationToken.None); + await RemoveComponentAsync(circuitHost, 1); // Assert Assert.Throws(() => ((TestRemoteRenderer)circuitHost.Renderer).GetTestComponentState(0)); } - private async Task AddComponent(CircuitHost circuitHost, Dictionary parameters) - where TComponent : IComponent + private async Task AddComponentAsync(CircuitHost circuitHost, int ssrComponentId, Dictionary parameters = null, string componentKey = "") + where TComponent : IComponent { var addOperation = new RootComponentOperation { Type = RootComponentOperationType.Add, - SelectorId = 1, - Marker = CreateMarker(typeof(TComponent), parameters), + SsrComponentId = ssrComponentId, + Marker = CreateMarker(typeof(TComponent), ssrComponentId.ToString(CultureInfo.InvariantCulture), parameters, componentKey), + Descriptor = new( + componentType: typeof(TComponent), + parameters: CreateWebRootComponentParameters(parameters)), + }; + + // Add component + await circuitHost.UpdateRootComponents( + new() { Operations = [addOperation] }, null, CreateDeserializer(), CancellationToken.None); + } + + private async Task UpdateComponentAsync(CircuitHost circuitHost, int ssrComponentId, Dictionary parameters = null, string componentKey = "") + { + var updateOperation = new RootComponentOperation + { + Type = RootComponentOperationType.Update, + SsrComponentId = ssrComponentId, + Marker = CreateMarker(typeof(TComponent), ssrComponentId.ToString(CultureInfo.InvariantCulture), parameters, componentKey), + Descriptor = new( + componentType: typeof(TComponent), + parameters: CreateWebRootComponentParameters(parameters)), }; - var addDescriptor = new ComponentDescriptor() + + // Update component + await circuitHost.UpdateRootComponents( + new() { Operations = [updateOperation] }, null, CreateDeserializer(), CancellationToken.None); + } + + private async Task RemoveComponentAsync(CircuitHost circuitHost, int ssrComponentId) + { + var removeOperation = new RootComponentOperation { - ComponentType = typeof(TComponent), - Parameters = ParameterView.FromDictionary(parameters), - Sequence = 0, + Type = RootComponentOperationType.Remove, + SsrComponentId = ssrComponentId, }; - // Add component + // Remove component await circuitHost.UpdateRootComponents( - [(addOperation, addDescriptor)], null, CreateDeserializer(), CancellationToken.None); + new() { Operations = [removeOperation] }, null, CreateDeserializer(), CancellationToken.None); } private ProtectedPrerenderComponentApplicationStore CreateStore() @@ -628,10 +671,11 @@ .Verifiable(); } - private ComponentMarker CreateMarker(Type type, Dictionary parameters = null) + private ComponentMarker CreateMarker(Type type, string locationHash, Dictionary parameters = null, string componentKey = "") { var serializer = new ServerComponentSerializer(_ephemeralDataProtectionProvider); - var marker = ComponentMarker.Create(ComponentMarker.ServerMarkerType, false, null); + var key = new ComponentMarkerKey(locationHash, componentKey); + var marker = ComponentMarker.Create(ComponentMarker.ServerMarkerType, false, key); serializer.SerializeInvocation( ref marker, _invocationSequence, @@ -640,6 +684,27 @@ return marker; } + private static WebRootComponentParameters CreateWebRootComponentParameters(IDictionary parameters = null) + { + if (parameters is null) + { + return WebRootComponentParameters.Empty; + } + + var parameterView = ParameterView.FromDictionary(parameters); + var (parameterDefinitions, parameterValues) = ComponentParameter.FromParameterView(parameterView); + for (var i = 0; i < parameterValues.Count; i++) + { + // WebRootComponentParameters expects serialized parameter values to be JsonElements. + var jsonElement = JsonSerializer.SerializeToElement(parameterValues[i]); + parameterValues[i] = jsonElement; + } + return new WebRootComponentParameters( + parameterView, + parameterDefinitions.AsReadOnly(), + parameterValues.AsReadOnly()); + } + private class TestRemoteRenderer : RemoteRenderer { public TestRemoteRenderer(IServiceProvider serviceProvider, IClientProxy client) @@ -789,15 +854,9 @@ return true; } - public bool TryDeserializeRootComponentOperations(string serializedComponentOperations, out (RootComponentOperation, ComponentDescriptor)[] operationsWithDescriptors) - { - operationsWithDescriptors= default; - return true; - } - - public bool TryDeserializeSingleComponentDescriptor(ComponentMarker record, [NotNullWhen(true)] out ComponentDescriptor result) + public bool TryDeserializeRootComponentOperations(string serializedComponentOperations, out RootComponentOperationBatch operationBatch) { - result = default; + operationBatch = default; return true; } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/test/Circuits/ComponentHubTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/test/Circuits/ComponentHubTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/test/Circuits/ComponentHubTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/test/Circuits/ComponentHubTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -170,17 +170,11 @@ return true; } - public bool TryDeserializeRootComponentOperations(string serializedComponentOperations, out (RootComponentOperation, ComponentDescriptor)[] operationsWithDescriptors) + public bool TryDeserializeRootComponentOperations(string serializedComponentOperations, out RootComponentOperationBatch operationsWithDescriptors) { operationsWithDescriptors = default; return true; } - - public bool TryDeserializeSingleComponentDescriptor(ComponentMarker record, [NotNullWhen(true)] out ComponentDescriptor result) - { - result = default; - return true; - } } private class TestCircuitFactory : ICircuitFactory diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/test/Circuits/RemoteRendererTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/test/Circuits/RemoteRendererTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/test/Circuits/RemoteRendererTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/test/Circuits/RemoteRendererTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Globalization; using System.Text.Json; using Microsoft.AspNetCore.Components.Endpoints; using Microsoft.AspNetCore.Components.Rendering; @@ -24,6 +25,8 @@ // failures. private static readonly TimeSpan Timeout = Debugger.IsAttached ? System.Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(10); + private const int MaxInteractiveServerRootComponentCount = 3; + private readonly IDataProtectionProvider _ephemeralDataProtectionProvider = new EphemeralDataProtectionProvider(); [Fact] @@ -425,6 +428,210 @@ exception.Message); } + [Fact] + public async Task WebRootComponentManager_AddRootComponentAsync_Throws_IfMaxInteractiveServerComponentCountIsExceeded() + { + // Arrange + var serviceProvider = CreateServiceProvider(); + var renderer = GetRemoteRenderer(serviceProvider); + + // Act + for (var i = 0; i < MaxInteractiveServerRootComponentCount; i++) + { + await AddWebRootComponentAsync(renderer, i); + } + + // Assert + var ex = await Assert.ThrowsAsync(() => AddWebRootComponentAsync(renderer, MaxInteractiveServerRootComponentCount)); + + Assert.Equal("Exceeded the maximum number of allowed server interactive root components.", ex.Message); + } + + [Fact] + public async Task WebRootComponentManager_AddRootComponentAsync_Throws_IfDuplicateSsrComponentIdIsProvided() + { + // Arrange + var serviceProvider = CreateServiceProvider(); + var renderer = GetRemoteRenderer(serviceProvider); + + // Act + await AddWebRootComponentAsync(renderer, 0); + + // Assert + var ex = await Assert.ThrowsAsync(() => AddWebRootComponentAsync(renderer, 0)); + + Assert.Equal("A root component with SSR component ID 0 already exists.", ex.Message); + } + + [Fact] + public async Task WebRootComponentManager_AddRootComponentAsync_Throws_IfKeyIsInvalid() + { + // Arrange + var serviceProvider = CreateServiceProvider(); + var renderer = GetRemoteRenderer(serviceProvider); + + // Act/assert + var ex = await Assert.ThrowsAsync(async () => + { + var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager(); + await webRootComponentManager.AddRootComponentAsync( + 0, + typeof(TestComponent), + default, // Invalid key + WebRootComponentParameters.Empty); + }); + + Assert.Equal("An invalid component marker key was provided.", ex.Message); + } + + [Fact] + public async Task WebRootComponentManager_AddRootComponentAsync_CanAddAndRenderRootComponent() + { + // Arrange + var serviceProvider = CreateServiceProvider(); + var renderer = GetRemoteRenderer(serviceProvider); + + // Act + await AddWebRootComponentAsync(renderer, 0); + + // Assert + Assert.Single(renderer._unacknowledgedRenderBatches); + } + + [Fact] + public async Task WebRootComponentManager_UpdateRootComponentAsync_Throws_IfSsrComponentIdIsInvalid() + { + // Arrange + var serviceProvider = CreateServiceProvider(); + var renderer = GetRemoteRenderer(serviceProvider); + + // Act + var key = await AddWebRootComponentAsync(renderer, 0); + + // Assert + var ex = await Assert.ThrowsAsync(async () => + { + var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager(); + await webRootComponentManager.UpdateRootComponentAsync(1, typeof(TestComponent), key, WebRootComponentParameters.Empty); + }); + + Assert.Equal($"No root component exists with SSR component ID 1.", ex.Message); + } + + [Fact] + public async Task WebRootComponentManager_UpdateRootComponentAsync_Throws_IfKeyDoesNotMatch() + { + // Arrange + var serviceProvider = CreateServiceProvider(); + var renderer = GetRemoteRenderer(serviceProvider); + + // Act + await AddWebRootComponentAsync(renderer, 0); + + // Assert + var ex = await Assert.ThrowsAsync(async () => + { + var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager(); + await webRootComponentManager.UpdateRootComponentAsync(0, typeof(TestComponent), new("1", null), WebRootComponentParameters.Empty); + }); + + Assert.Equal("Cannot update components with mismatching keys.", ex.Message); + } + + [Fact] + public async Task WebRootComponentManager_UpdateRootComponentAsync_Works_IfComponentKeyWasSupplied() + { + // Arrange + var serviceProvider = CreateServiceProvider(); + var renderer = GetRemoteRenderer(serviceProvider); + + // Act + var key = await AddWebRootComponentAsync(renderer, 0, "mykey"); + await renderer.Dispatcher.InvokeAsync(() => + { + var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager(); + var parameters = new Dictionary { ["Name"] = "value" }; + webRootComponentManager.UpdateRootComponentAsync(0, typeof(TestComponent), key, CreateWebRootComponentParameters(parameters)); + }); + + // Assert + Assert.Equal(2, renderer._unacknowledgedRenderBatches.Count); // Initial render, re-render + } + + [Fact] + public async Task WebRootComponentManager_UpdateRootComponentAsync_DoesNothing_IfNoComponentKeyWasSuppliedAndParametersDidNotChange() + { + // Arrange + var serviceProvider = CreateServiceProvider(); + var renderer = GetRemoteRenderer(serviceProvider); + + // Act + var key = await AddWebRootComponentAsync(renderer, 0); + await renderer.Dispatcher.InvokeAsync(() => + { + var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager(); + webRootComponentManager.UpdateRootComponentAsync(0, typeof(TestComponent), key, WebRootComponentParameters.Empty); + }); + + // Assert + Assert.Single(renderer._unacknowledgedRenderBatches); + } + + [Fact] + public async Task WebRootComponentManager_UpdateRootComponentAsync_ReinitializesComponent_IfNoComponentKeyWasSuppliedAndParameterChanged() + { + // Arrange + var serviceProvider = CreateServiceProvider(); + var renderer = GetRemoteRenderer(serviceProvider); + + // Act + var key = await AddWebRootComponentAsync(renderer, 0); + await renderer.Dispatcher.InvokeAsync(() => + { + var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager(); + var parameters = new Dictionary { ["Name"] = "value" }; + webRootComponentManager.UpdateRootComponentAsync(0, typeof(TestComponent), key, CreateWebRootComponentParameters(parameters)); + }); + + // Assert + Assert.Equal(3, renderer._unacknowledgedRenderBatches.Count); // Initial render, dispose, and re-initialize + } + + [Fact] + public async Task WebRootComponentManager_RemoveRootComponent_Throws_IfSsrComponentIdIsInvalid() + { + // Arrange + var serviceProvider = CreateServiceProvider(); + var renderer = GetRemoteRenderer(serviceProvider); + + // Act + var key = await AddWebRootComponentAsync(renderer, 0); + + // Assert + var ex = Assert.Throws(() => renderer.GetOrCreateWebRootComponentManager().RemoveRootComponent(1)); + + Assert.Equal($"No root component exists with SSR component ID 1.", ex.Message); + } + + [Fact] + public async Task WebRootComponentManager_RemoveRootComponent_Works() + { + // Arrange + var serviceProvider = CreateServiceProvider(); + var renderer = GetRemoteRenderer(serviceProvider); + + // Act + var key = await AddWebRootComponentAsync(renderer, 0); + await renderer.Dispatcher.InvokeAsync(() => + { + var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager(); + webRootComponentManager.RemoveRootComponent(0); + }); + + // Assert + Assert.Equal(2, renderer._unacknowledgedRenderBatches.Count); // Initial render, dispose + } + private IServiceProvider CreateServiceProvider() { var serviceCollection = new ServiceCollection(); @@ -445,12 +652,51 @@ return new TestRemoteRenderer( serviceProvider, NullLoggerFactory.Instance, - new CircuitOptions(), + new CircuitOptions + { + RootComponents = + { + MaxJSRootComponents = MaxInteractiveServerRootComponentCount + }, + }, circuitClient ?? new CircuitClientProxy(), serverComponentDeserializer, NullLogger.Instance); } + private static Task AddWebRootComponentAsync(RemoteRenderer renderer, int ssrComponentId, string componentKey = null) + => renderer.Dispatcher.InvokeAsync(async () => + { + var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager(); + var componentMarkerKey = new ComponentMarkerKey() + { + LocationHash = ssrComponentId.ToString(CultureInfo.CurrentCulture), + FormattedComponentKey = componentKey, + }; + await webRootComponentManager.AddRootComponentAsync( + ssrComponentId, + typeof(TestComponent), + componentMarkerKey, + WebRootComponentParameters.Empty); + return componentMarkerKey; + }); + + private static WebRootComponentParameters CreateWebRootComponentParameters(IDictionary parameters) + { + var parameterView = ParameterView.FromDictionary(parameters); + var (parameterDefinitions, parameterValues) = ComponentParameter.FromParameterView(parameterView); + for (var i = 0; i < parameterValues.Count; i++) + { + // WebRootComponentParameters expects serialized parameter values to be JsonElements. + var jsonElement = JsonSerializer.SerializeToElement(parameterValues[i]); + parameterValues[i] = jsonElement; + } + return new WebRootComponentParameters( + parameterView, + parameterDefinitions.AsReadOnly(), + parameterValues.AsReadOnly()); + } + private class TestRemoteRenderer : RemoteRenderer { public TestRemoteRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, CircuitOptions options, CircuitClientProxy client, IServerComponentDeserializer serverComponentDeserializer, ILogger logger) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/test/Circuits/ServerComponentDeserializerTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/test/Circuits/ServerComponentDeserializerTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Server/test/Circuits/ServerComponentDeserializerTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Server/test/Circuits/ServerComponentDeserializerTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -304,20 +304,19 @@ } [Fact] - public void TryDeserializeSingleComponentDescriptor_CanParseSingleMarker() + public void TryDeserializeWebRootComponentDescriptor_CanParseSingleMarker() { // Arrange var markers = CreateMarkers(typeof(TestComponent)); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert - Assert.True(serverComponentDeserializer.TryDeserializeSingleComponentDescriptor(markers[0], out var descriptor)); + Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(markers[0], out var descriptor)); Assert.Equal(typeof(TestComponent).FullName, descriptor.ComponentType.FullName); - Assert.Equal(0, descriptor.Sequence); } [Fact] - public void TryDeserializeSingleComponentDescriptor_CanParseMultipleMarkersWithAndWithoutParameters() + public void TryDeserializeWebRootComponentDescriptor_CanParseMultipleMarkersWithAndWithoutParameters() { // Arrange var markers = CreateMarkers( @@ -326,35 +325,33 @@ var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert - Assert.True(serverComponentDeserializer.TryDeserializeSingleComponentDescriptor(markers[0], out var firstDescriptor)); - Assert.True(serverComponentDeserializer.TryDeserializeSingleComponentDescriptor(markers[1], out var secondDescriptor)); + Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(markers[0], out var firstDescriptor)); + Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(markers[1], out var secondDescriptor)); Assert.Equal(typeof(TestComponent).FullName, firstDescriptor.ComponentType.FullName); - Assert.Equal(0, firstDescriptor.Sequence); - var firstParameters = firstDescriptor.Parameters.ToDictionary(); + var firstParameters = firstDescriptor.Parameters.Parameters.ToDictionary(); Assert.Single(firstParameters); Assert.Contains("First", firstParameters.Keys); Assert.Equal("Value", firstParameters["First"]); Assert.Equal(typeof(TestComponent).FullName, secondDescriptor.ComponentType.FullName); - Assert.Equal(1, secondDescriptor.Sequence); - Assert.Empty(secondDescriptor.Parameters.ToDictionary()); + Assert.Empty(secondDescriptor.Parameters.Parameters.ToDictionary()); } [Fact] - public void TryDeserializeSingleComponentDescriptor_AllowsParsingMarkersOutOfOrder() + public void TryDeserializeWebRootComponentDescriptor_AllowsParsingMarkersOutOfOrder() { // Arrange var markers = CreateMarkers(typeof(TestComponent), typeof(TestComponent)); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert - Assert.True(serverComponentDeserializer.TryDeserializeSingleComponentDescriptor(markers[1], out _)); - Assert.True(serverComponentDeserializer.TryDeserializeSingleComponentDescriptor(markers[0], out _)); + Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(markers[1], out _)); + Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(markers[0], out _)); } [Fact] - public void TryDeserializeSingleComponentDescriptor_AllowsParsingMarkersFromMultipleInvocations() + public void TryDeserializeWebRootComponentDescriptor_AllowsParsingMarkersFromMultipleInvocations() { // Arrange var firstInvocationMarkers = CreateMarkers(typeof(TestComponent)); @@ -363,24 +360,24 @@ var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert - Assert.True(serverComponentDeserializer.TryDeserializeSingleComponentDescriptor(firstInvocationMarkers[0], out _)); - Assert.True(serverComponentDeserializer.TryDeserializeSingleComponentDescriptor(secondInvocationMarkers[0], out _)); + Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(firstInvocationMarkers[0], out _)); + Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(secondInvocationMarkers[0], out _)); } [Fact] - public void TryDeserializeSingleComponentDescriptor_DoesNotParseTheSameMarkerTwice() + public void TryDeserializeWebRootComponentDescriptor_DoesNotParseTheSameMarkerTwice() { // Arrange var markers = CreateMarkers(typeof(TestComponent)); var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert - Assert.True(serverComponentDeserializer.TryDeserializeSingleComponentDescriptor(markers[0], out _)); - Assert.False(serverComponentDeserializer.TryDeserializeSingleComponentDescriptor(markers[0], out _)); + Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(markers[0], out _)); + Assert.False(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(markers[0], out _)); } [Fact] - public void TryDeserializeSingleComponentDescriptor_DoesNotParseMarkerFromOldInvocation() + public void TryDeserializeWebRootComponentDescriptor_DoesNotParseMarkerFromOldInvocation() { // Arrange var firstInvocationMarkers = CreateMarkers(typeof(TestComponent), typeof(TestComponent)); @@ -389,71 +386,19 @@ var serverComponentDeserializer = CreateServerComponentDeserializer(); // Act & assert - Assert.True(serverComponentDeserializer.TryDeserializeSingleComponentDescriptor(firstInvocationMarkers[0], out _)); - Assert.True(serverComponentDeserializer.TryDeserializeSingleComponentDescriptor(secondInvocationMarkers[0], out _)); - Assert.False(serverComponentDeserializer.TryDeserializeSingleComponentDescriptor(firstInvocationMarkers[0], out _)); + Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(firstInvocationMarkers[0], out _)); + Assert.True(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(secondInvocationMarkers[0], out _)); + Assert.False(serverComponentDeserializer.TryDeserializeWebRootComponentDescriptor(firstInvocationMarkers[0], out _)); } [Fact] - public void UpdateRootComponents_TryDeserializeRootComponentOperationsReturnsFalse_WhenAddOperationIsMissingSelectorId() - { - // Arrange - var operation = new RootComponentOperation - { - Type = RootComponentOperationType.Add, - SelectorId = 1, - Marker = new ComponentMarker() - { - Descriptor = "some random text", - }, - }; - var operationsJson = JsonSerializer.Serialize( - new[] { operation }, - ServerComponentSerializationSettings.JsonSerializationOptions); - var deserializer = CreateServerComponentDeserializer(); - - // Act - var result = deserializer.TryDeserializeRootComponentOperations(operationsJson, out var parsed); - - // Assert - Assert.False(result); - Assert.Null(parsed); - } - - [Fact] - public void UpdateRootComponents_TryDeserializeRootComponentOperationsReturnsFalse_WhenComponentIdIsMissing() - { - // Arrange - var operation = new RootComponentOperation - { - Type = RootComponentOperationType.Update, - Marker = CreateMarker(typeof(DynamicallyAddedComponent), new() - { - ["Message"] = "Some other message", - }), - }; - var operationsJson = JsonSerializer.Serialize( - new[] { operation }, - ServerComponentSerializationSettings.JsonSerializationOptions); - - var deserializer = CreateServerComponentDeserializer(); - - // Act - var result = deserializer.TryDeserializeRootComponentOperations(operationsJson, out var parsed); - - // Assert - Assert.False(result); - Assert.Null(parsed); - } - - [Fact] - public void UpdateRootComponents_TryDeserializeRootComponentOperationsReturnsFalse_WhenComponentIdIsRepeated() + public void UpdateRootComponents_TryDeserializeRootComponentOperationsReturnsFalse_WhenSsrComponentIdIsRepeated() { // Arrange var operation = new RootComponentOperation { Type = RootComponentOperationType.Update, - ComponentId = 1, + SsrComponentId = 1, Marker = CreateMarker(typeof(DynamicallyAddedComponent), new() { ["Message"] = "Some other message", @@ -463,18 +408,15 @@ var other = new RootComponentOperation { Type = RootComponentOperationType.Remove, - ComponentId = 1, + SsrComponentId = 1, Marker = CreateMarker(typeof(DynamicallyAddedComponent)), }; - var operationsJson = JsonSerializer.Serialize( - new[] { operation, other }, - ServerComponentSerializationSettings.JsonSerializationOptions); - + var batchJson = SerializeRootComponentOperationBatch(new() { Operations = [operation, other] }); var deserializer = CreateServerComponentDeserializer(); // Act - var result = deserializer.TryDeserializeRootComponentOperations(operationsJson, out var parsed); + var result = deserializer.TryDeserializeRootComponentOperations(batchJson, out var parsed); // Assert Assert.False(result); @@ -483,9 +425,12 @@ private string SerializeComponent(string assembly, string type) => JsonSerializer.Serialize( - new ServerComponent(0, assembly, type, Array.Empty(), Array.Empty(), Guid.NewGuid()), + new ServerComponent(0, null, assembly, type, Array.Empty(), Array.Empty(), Guid.NewGuid()), ServerComponentSerializationSettings.JsonSerializationOptions); + private string SerializeRootComponentOperationBatch(RootComponentOperationBatch batch) + => JsonSerializer.Serialize(batch, ServerComponentSerializationSettings.JsonSerializationOptions); + private ServerComponentDeserializer CreateServerComponentDeserializer() { return new ServerComponentDeserializer( @@ -501,7 +446,8 @@ private ComponentMarker CreateMarker(Type type, Dictionary parameters = null) { var serializer = new ServerComponentSerializer(_ephemeralDataProtectionProvider); - var marker = ComponentMarker.Create(ComponentMarker.ServerMarkerType, false, null); + var key = new ComponentMarkerKey(type.FullName, null); + var marker = ComponentMarker.Create(ComponentMarker.ServerMarkerType, false, key); serializer.SerializeInvocation( ref marker, _invocationSequence, diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Shared/src/DefaultAntiforgeryStateProvider.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Shared/src/DefaultAntiforgeryStateProvider.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Shared/src/DefaultAntiforgeryStateProvider.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Shared/src/DefaultAntiforgeryStateProvider.cs 2023-11-13 13:20:34.000000000 +0000 @@ -24,9 +24,9 @@ // don't have access to the request. _subscription = state.RegisterOnPersisting(() => { - state.PersistAsJson(PersistenceKey, _currentToken); + state.PersistAsJson(PersistenceKey, GetAntiforgeryToken()); return Task.CompletedTask; - }, new InteractiveAutoRenderMode()); + }, RenderMode.InteractiveAuto); state.TryTakeFromJson(PersistenceKey, out _currentToken); } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Shared/src/RootComponentOperationBatch.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Shared/src/RootComponentOperationBatch.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Shared/src/RootComponentOperationBatch.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Shared/src/RootComponentOperationBatch.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Components; + +internal sealed class RootComponentOperationBatch +{ + public long BatchId { get; set; } + + public required RootComponentOperation[] Operations { get; set; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Shared/src/RootComponentOperation.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Shared/src/RootComponentOperation.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Shared/src/RootComponentOperation.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Shared/src/RootComponentOperation.cs 2023-11-13 13:20:34.000000000 +0000 @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Json.Serialization; + namespace Microsoft.AspNetCore.Components; internal sealed class RootComponentOperation @@ -8,15 +10,23 @@ // Represents the type of root component operation to perform. public RootComponentOperationType Type { get; set; } - // When adding a root component, this is the selector ID - // to round-trip back to the client so it knows which DOM - // element the component should be attached to. - public int? SelectorId { get; set; } - - // The ID of the component to use during an update or remove - // operation. - public int? ComponentId { get; set; } + // The client side ID of the component to perform the operation on. + public int SsrComponentId { get; set; } // The marker that was initially rendered to the page. public ComponentMarker? Marker { get; set; } + + // Describes additional information about the component. + // This property may get populated by .NET after JSON deserialization. + [JsonIgnore] + public WebRootComponentDescriptor? Descriptor { get; set; } +} + +internal sealed class WebRootComponentDescriptor( + Type componentType, + WebRootComponentParameters parameters) +{ + public Type ComponentType { get; } = componentType; + + public WebRootComponentParameters Parameters { get; } = parameters; } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Shared/src/WebRootComponentManager.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Shared/src/WebRootComponentManager.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Shared/src/WebRootComponentManager.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Shared/src/WebRootComponentManager.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,188 @@ +// 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.CodeAnalysis; +using System.Globalization; +using static Microsoft.AspNetCore.Internal.LinkerFlags; + +#if COMPONENTS_SERVER +namespace Microsoft.AspNetCore.Components.Server.Circuits; + +using Renderer = RemoteRenderer; + +internal partial class RemoteRenderer +#elif COMPONENTS_WEBASSEMBLY +namespace Microsoft.AspNetCore.Components.WebAssembly.Rendering; + +using Renderer = WebAssemblyRenderer; + +internal partial class WebAssemblyRenderer +#else +#error WebRootComponentManager cannot be defined in this assembly. +#endif +{ + private WebRootComponentManager? _webRootComponentManager; + + public WebRootComponentManager GetOrCreateWebRootComponentManager() + => _webRootComponentManager ??= new(this); + + // Manages components that get added, updated, or removed in Blazor Web scenarios + // via Blazor endpoint invocations. + public sealed class WebRootComponentManager(Renderer renderer) + { + private readonly Dictionary _webRootComponents = new(); + + public async Task AddRootComponentAsync( + int ssrComponentId, + [DynamicallyAccessedMembers(Component)] Type componentType, + ComponentMarkerKey? key, + WebRootComponentParameters parameters) + { +#if COMPONENTS_SERVER + if (_webRootComponents.Count + 1 > renderer._options.RootComponents.MaxJSRootComponents) + { + throw new InvalidOperationException("Exceeded the maximum number of allowed server interactive root components."); + } +#endif + + if (_webRootComponents.ContainsKey(ssrComponentId)) + { + throw new InvalidOperationException($"A root component with SSR component ID {ssrComponentId} already exists."); + } + + var component = WebRootComponent.Create(renderer, componentType, ssrComponentId, key, parameters); + _webRootComponents.Add(ssrComponentId, component); + + await component.RenderAsync(renderer); + } + + public Task UpdateRootComponentAsync( + int ssrComponentId, + [DynamicallyAccessedMembers(Component)] Type newComponentType, + ComponentMarkerKey? newKey, + WebRootComponentParameters newParameters) + { + var component = GetRequiredWebRootComponent(ssrComponentId); + return component.UpdateAsync(renderer, newComponentType, newKey, newParameters); + } + + public void RemoveRootComponent(int ssrComponentId) + { + var component = GetRequiredWebRootComponent(ssrComponentId); + component.Remove(renderer); + _webRootComponents.Remove(ssrComponentId); + } + + private WebRootComponent GetRequiredWebRootComponent(int ssrComponentId) + { + if (!_webRootComponents.TryGetValue(ssrComponentId, out var component)) + { + throw new InvalidOperationException($"No root component exists with SSR component ID {ssrComponentId}."); + } + + return component; + } + + private sealed class WebRootComponent + { + [DynamicallyAccessedMembers(Component)] + private readonly Type _componentType; + private readonly string _ssrComponentIdString; + private readonly ComponentMarkerKey _key; + + private WebRootComponentParameters _latestParameters; + private int _interactiveComponentId; + + public static WebRootComponent Create( + Renderer renderer, + [DynamicallyAccessedMembers(Component)] Type componentType, + int ssrComponentId, + ComponentMarkerKey? key, + WebRootComponentParameters initialParameters) + { + if (!key.HasValue) + { + throw new InvalidOperationException("An invalid component marker key was provided."); + } + + var ssrComponentIdString = ssrComponentId.ToString(CultureInfo.InvariantCulture); + var interactiveComponentId = renderer.AddRootComponent(componentType, ssrComponentIdString); + + return new(componentType, ssrComponentIdString, key.Value, interactiveComponentId, initialParameters); + } + + private WebRootComponent( + [DynamicallyAccessedMembers(Component)] Type componentType, + string ssrComponentIdString, + ComponentMarkerKey key, + int interactiveComponentId, + in WebRootComponentParameters initialParameters) + { + _componentType = componentType; + _ssrComponentIdString = ssrComponentIdString; + _key = key; + _interactiveComponentId = interactiveComponentId; + _latestParameters = initialParameters; + } + + public Task UpdateAsync( + Renderer renderer, + [DynamicallyAccessedMembers(Component)] Type newComponentType, + ComponentMarkerKey? newKey, + WebRootComponentParameters newParameters) + { + if (_componentType != newComponentType) + { + throw new InvalidOperationException("Cannot update components with mismatching types."); + } + + if (!newKey.HasValue) + { + throw new InvalidOperationException("An invalid component marker key was provided."); + } + + if (!string.Equals(_key.LocationHash, newKey.Value.LocationHash, StringComparison.Ordinal) || + !string.Equals(_key.FormattedComponentKey, newKey.Value.FormattedComponentKey, StringComparison.Ordinal)) + { + // The client should always supply updated parameters to a component with a matching key. + throw new InvalidOperationException("Cannot update components with mismatching keys."); + } + + if (!string.IsNullOrEmpty(_key.FormattedComponentKey)) + { + // We can supply new parameters if the key has a @key value, because that means the client + // opted in to dynamic parameter updates. + _latestParameters = newParameters; + return RenderAsync(renderer); + } + else + { + if (_latestParameters.DefinitelyEquals(newParameters)) + { + // The parameters haven't changed, so there's no work to do. + return Task.CompletedTask; + } + else + { + // The component parameters have changed. Rather than update the existing instance, we'll dispose + // it and replace it with a new one. This is because it's the client's choice how to + // match prerendered components with existing components, and we don't want to allow + // clients to maliciously assign parameters to the wrong component. + renderer.RemoveRootComponent(_interactiveComponentId); + _interactiveComponentId = renderer.AddRootComponent(_componentType, _ssrComponentIdString); + _latestParameters = newParameters; + return RenderAsync(renderer); + } + } + } + + public Task RenderAsync(Renderer renderer) + => renderer.RenderRootComponentAsync(_interactiveComponentId, _latestParameters.Parameters); + + public void Remove(Renderer renderer) + { + renderer.RemoveRootComponent(_interactiveComponentId); + } + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Shared/src/WebRootComponentParameters.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Shared/src/WebRootComponentParameters.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Shared/src/WebRootComponentParameters.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Shared/src/WebRootComponentParameters.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; + +namespace Microsoft.AspNetCore.Components; + +internal readonly struct WebRootComponentParameters( + ParameterView parameterView, + IReadOnlyList parameterDefinitions, + IReadOnlyList serializedParameterValues) +{ + public static readonly WebRootComponentParameters Empty = new(ParameterView.Empty, [], []); + + // The parameter definitions and values are assumed to be the same length. + private readonly IReadOnlyList _parameterDefinitions = parameterDefinitions; + private readonly IReadOnlyList _serializedParameterValues = serializedParameterValues; + + public ParameterView Parameters => parameterView; + + // Unlike the equality checking implementation in ParameterView, this method + // compares the serialized values of parameters. This is because it's a requirement that + // component parameters for interactive root components are serializable, so we can + // compare the serialized representation without relying on .NET equality checking + // for the deserialized values, which may yield false negatives. + public bool DefinitelyEquals(in WebRootComponentParameters other) + { + var count = _parameterDefinitions.Count; + if (count != other._parameterDefinitions.Count) + { + return false; + } + + for (var i = 0; i < count; i++) + { + // We rely on parameter definitions and values having a consistent order between + // multiple endpoint invocations. This should be true because component parameters + // are usually rendered in a deterministic order. + var definition = _parameterDefinitions[i]; + var otherDefinition = other._parameterDefinitions[i]; + if (!string.Equals(definition.Name, otherDefinition.Name, StringComparison.Ordinal) || + !string.Equals(definition.TypeName, otherDefinition.TypeName, StringComparison.Ordinal) || + !string.Equals(definition.Assembly, otherDefinition.Assembly, StringComparison.Ordinal)) + { + return false; + } + + var value = ((JsonElement)_serializedParameterValues[i]).GetRawText(); + var otherValue = ((JsonElement)other._serializedParameterValues[i]).GetRawText(); + if (!string.Equals(value, otherValue, StringComparison.Ordinal)) + { + return false; + } + } + + return true; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/Infrastructure/AssemblyInfo.AssemblyFixtures.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/Infrastructure/AssemblyInfo.AssemblyFixtures.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/Infrastructure/AssemblyInfo.AssemblyFixtures.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/Infrastructure/AssemblyInfo.AssemblyFixtures.cs 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -// 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.E2ETesting; -using Microsoft.AspNetCore.Testing; - -[assembly: AssemblyFixture(typeof(SeleniumStandaloneServer))] -[assembly: AssemblyFixture(typeof(SauceConnectServer))] diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/package.json dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/package.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/package.json 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/package.json 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -{ - "name": "microsoft.aspnetcore.components.e2etest", - "version": "0.0.1", - "description": "Not a real package. This file exists only to declare dependencies.", - "main": "index.js", - "private": true, - "scripts": { - "selenium-standalone": "selenium-standalone", - "prepare": "selenium-standalone install --config ../../../Shared/E2ETesting/selenium-config.json", - "sauce": "ts-node ./scripts/sauce.ts" - }, - "author": "", - "license": "MIT", - "dependencies": { - "sauce-connect-launcher": "^1.3.1", - "selenium-standalone": "^7.1.0" - }, - "devDependencies": { - "@types/node": "^13.1.7", - "ts-node": "^8.6.2", - "typescript": "^3.7.5" - }, - "resolutions": { - "lodash": ">=4.17.21" - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/scripts/sauce.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/scripts/sauce.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/scripts/sauce.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/scripts/sauce.ts 1970-01-01 00:00:00.000000000 +0000 @@ -1,82 +0,0 @@ -import { EOL } from "os"; -import * as _fs from "fs"; -import { promisify } from "util"; - -// Promisify things from fs we want to use. -const fs = { - createWriteStream: _fs.createWriteStream, - exists: promisify(_fs.exists), - mkdir: promisify(_fs.mkdir), - appendFile: promisify(_fs.appendFile), - readFile: promisify(_fs.readFile), -}; - -process.on("unhandledRejection", (reason) => { - console.error(`Unhandled promise rejection: ${reason}`); - process.exit(1); -}); - -let sauceUser = null; -let sauceKey = null; -let tunnelIdentifier = null; -let hostName = null; - -for (let i = 0; i < process.argv.length; i += 1) { - switch (process.argv[i]) { - case "--sauce-user": - i += 1; - sauceUser = process.argv[i]; - break; - case "--sauce-key": - i += 1; - sauceKey = process.argv[i]; - break; - case "--sauce-tunnel": - i += 1; - tunnelIdentifier = process.argv[i]; - break; - case "--use-hostname": - i += 1; - hostName = process.argv[i]; - break; - } -} - -const HOSTSFILE_PATH = process.platform === "win32" ? `${process.env.SystemRoot}\\System32\\drivers\\etc\\hosts` : null; - -(async () => { - - if (hostName) { - // Register a custom hostname in the hosts file (requires Admin, but AzDO agents run as Admin) - // Used to work around issues in Sauce Labs. - if (process.platform !== "win32") { - throw new Error("Can't use '--use-hostname' on non-Windows platform."); - } - - try { - - console.log(`Updating Hosts file (${HOSTSFILE_PATH}) to register host name '${hostName}'`); - await fs.appendFile(HOSTSFILE_PATH, `${EOL}127.0.0.1 ${hostName}${EOL}`); - - } catch (error) { - console.log(`Unable to update hosts file at ${HOSTSFILE_PATH}. Error: ${error}`); - } - } - - - // Creates a persistent proxy tunnel using Sauce Connect. - var sauceConnectLauncher = require('sauce-connect-launcher'); - - sauceConnectLauncher({ - username: sauceUser, - accessKey: sauceKey, - tunnelIdentifier: tunnelIdentifier, - }, function (err, sauceConnectProcess) { - if (err) { - console.error(err.message); - return; - } - - console.log("Sauce Connect ready"); - }); -})(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/ServerExecutionTests/JsInitializersTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/ServerExecutionTests/JsInitializersTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/ServerExecutionTests/JsInitializersTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/ServerExecutionTests/JsInitializersTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -4,6 +4,7 @@ using BasicTestApp; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.Components.E2ETest.Tests; +using Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests; using Microsoft.AspNetCore.E2ETesting; using Xunit.Abstractions; @@ -18,4 +19,17 @@ : base(browserFixture, serverFixture.WithServerExecution(), output) { } + + protected override string[] GetExpectedCallbacks() + { + return ["classic-before-start", + "classic-after-started", + "classic-and-modern-before-server-start", + "classic-and-modern-after-server-started", + "classic-and-modern-circuit-opened", + "modern-before-server-start", + "modern-after-server-started", + "modern-circuit-opened", + ]; + } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/BlazorWebJsInitializersTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/BlazorWebJsInitializersTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/BlazorWebJsInitializersTest.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/BlazorWebJsInitializersTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Components.TestServer.RazorComponents; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using TestServer; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests; + +public class BlazorWebJsInitializersTest : ServerTestBase>> +{ + public BlazorWebJsInitializersTest( + BrowserFixture browserFixture, + BasicTestAppServerSiteFixture> serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + } + + public override Task InitializeAsync() + { + return base.InitializeAsync(BrowserFixture.StreamingContext); + } + + [Theory] + [MemberData(nameof(InitializerTestData))] + public void InitializersRunsModernCallbacksByDefaultWhenPresent(bool streaming, bool webassembly, bool server, List expectedInvokedCallbacks) + { + var url = $"{ServerPathBase}/initializers?streaming={streaming}&wasm={webassembly}&server={server}"; + Navigate(url); + + foreach (var callback in expectedInvokedCallbacks) + { + Browser.Exists(By.Id(callback)); + } + + Browser.Equal(expectedInvokedCallbacks.Count, () => Browser.FindElements(By.CssSelector("#initializers-content > p")).Count); + + if (server) + { + Browser.Click(By.Id("remove-server-component")); + Browser.Exists(By.Id("classic-and-modern-circuit-closed")); + } + } + + [Theory] + [MemberData(nameof(InitializerTestData))] + public void InitializersRunsClassicInitializersWhenEnabled(bool streaming, bool webassembly, bool server, List expectedInvokedCallbacks) + { + EnableClassicInitializers(Browser); + List expectedCallbacks = ["classic-before-start", "classic-after-started", ..expectedInvokedCallbacks]; + var url = $"{ServerPathBase}/initializers?streaming={streaming}&wasm={webassembly}&server={server}"; + Navigate(url); + + foreach (var callback in expectedCallbacks) + { + Browser.Exists(By.Id(callback)); + } + + Browser.Equal(expectedCallbacks.Count, () => Browser.FindElements(By.CssSelector("#initializers-content > p")).Count); + + if (server) + { + Browser.Click(By.Id("remove-server-component")); + Browser.Exists(By.Id("classic-and-modern-circuit-closed")); + } + } + + private void EnableClassicInitializers(IWebDriver browser) + { + browser.Navigate().GoToUrl($"{new Uri(_serverFixture.RootUri, ServerPathBase)}/"); + ((IJavaScriptExecutor)browser).ExecuteScript("sessionStorage.setItem('enable-classic-initializers', 'true')"); + } + + public static TheoryData> InitializerTestData() + { + var result = new TheoryData> + { + { false, false, false, ["classic-and-modern-before-web-start", "classic-and-modern-after-web-started","modern-before-web-start", "modern-after-web-started"] }, + { false, false, true, ["classic-and-modern-before-web-start", "classic-and-modern-after-web-started", "classic-and-modern-before-server-start", "classic-and-modern-after-server-started", "classic-and-modern-circuit-opened", "modern-before-web-start", "modern-after-web-started", "modern-before-server-start", "modern-after-server-started", "modern-circuit-opened" ] }, + { false, true, false, ["classic-and-modern-before-web-start", "classic-and-modern-after-web-started", "classic-and-modern-before-web-assembly-start", "classic-and-modern-after-web-assembly-started", "modern-before-web-start", "modern-after-web-started", "modern-before-web-assembly-start", "modern-after-web-assembly-started"] }, + { false, true, true, ["classic-and-modern-before-web-start", "classic-and-modern-after-web-started", "classic-and-modern-before-server-start", "classic-and-modern-circuit-opened", "classic-and-modern-after-server-started", "classic-and-modern-before-web-assembly-start", "classic-and-modern-after-web-assembly-started", "modern-before-web-start", "modern-after-web-started", "modern-before-server-start", "modern-circuit-opened", "modern-after-server-started", "modern-before-web-assembly-start", "modern-after-web-assembly-started"] }, + { true, false, false, ["classic-and-modern-before-web-start", "classic-and-modern-after-web-started","modern-before-web-start", "modern-after-web-started"] }, + { true, false, true, ["classic-and-modern-before-web-start", "classic-and-modern-after-web-started", "classic-and-modern-before-server-start", "classic-and-modern-after-server-started", "classic-and-modern-circuit-opened", "modern-before-web-start", "modern-after-web-started", "modern-before-server-start", "modern-after-server-started", "modern-circuit-opened" ] }, + { true, true, false, ["classic-and-modern-before-web-start", "classic-and-modern-after-web-started", "classic-and-modern-before-web-assembly-start", "classic-and-modern-after-web-assembly-started", "modern-before-web-start", "modern-after-web-started", "modern-before-web-assembly-start", "modern-after-web-assembly-started"] }, + { true, true, true, ["classic-and-modern-before-web-start", "classic-and-modern-after-web-started", "classic-and-modern-before-server-start", "classic-and-modern-circuit-opened", "classic-and-modern-after-server-started", "classic-and-modern-before-web-assembly-start", "classic-and-modern-after-web-assembly-started", "modern-before-web-start", "modern-after-web-started", "modern-before-server-start", "modern-circuit-opened", "modern-after-server-started", "modern-before-web-assembly-start", "modern-after-web-assembly-started"] }, + }; + + return result; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/EndpointsServerReconnectionTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/EndpointsServerReconnectionTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/EndpointsServerReconnectionTest.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/EndpointsServerReconnectionTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,203 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Components.TestServer.RazorComponents; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using TestServer; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests; + +[CollectionDefinition(nameof(InteractivityTest), DisableParallelization = true)] +public class EndpointsServerReconnectionTest : ServerTestBase>> +{ + public EndpointsServerReconnectionTest( + BrowserFixture browserFixture, + BasicTestAppServerSiteFixture> serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + } + + public override Task InitializeAsync() + => InitializeAsync(BrowserFixture.StreamingContext); + + [Fact] + public void ReconnectUI_Displays_OnFirstReconnect() + { + Navigate($"{ServerPathBase}/streaming-interactivity"); + + Browser.Equal("Not streaming", () => Browser.FindElement(By.Id("status")).Text); + + Browser.Click(By.Id("add-server-counter-prerendered-link")); + Browser.Equal("True", () => Browser.FindElement(By.Id("is-interactive-0")).Text); + + var javascript = (IJavaScriptExecutor)Browser; + + javascript.ExecuteScript("Blazor._internal.forceCloseConnection()"); + Browser.Equal("block", () => Browser.Exists(By.Id("components-reconnect-modal")).GetCssValue("display")); + Browser.Equal("none", () => Browser.Exists(By.Id("components-reconnect-modal")).GetCssValue("display")); + Browser.Exists(By.Id("increment-0")).Click(); + Browser.Equal("1", () => Browser.Exists(By.Id("count-0")).Text); + } + + [Fact] + public void ReconnectUI_Displays_OnSuccessiveReconnects_AfterEnhancedNavigation() + { + Navigate($"{ServerPathBase}/streaming-interactivity"); + + Browser.Equal("Not streaming", () => Browser.FindElement(By.Id("status")).Text); + + Browser.Click(By.Id("add-server-counter-prerendered-link")); + Browser.Equal("True", () => Browser.FindElement(By.Id("is-interactive-0")).Text); + + var javascript = (IJavaScriptExecutor)Browser; + + // Perform the first reconnect + javascript.ExecuteScript("Blazor._internal.forceCloseConnection()"); + Browser.Equal("block", () => Browser.Exists(By.Id("components-reconnect-modal")).GetCssValue("display")); + Browser.Equal("none", () => Browser.Exists(By.Id("components-reconnect-modal")).GetCssValue("display")); + Browser.Exists(By.Id("increment-0")).Click(); + Browser.Equal("1", () => Browser.Exists(By.Id("count-0")).Text); + + // Perform an enhanced navigation by updating the component's parameters + Browser.Exists(By.Id("update-counter-link-0")).Click(); + Browser.Equal("2", () => Browser.FindElement(By.Id("increment-amount-0")).Text); + + // Perform the second reconnect + javascript.ExecuteScript("Blazor._internal.forceCloseConnection()"); + Browser.Equal("block", () => Browser.Exists(By.Id("components-reconnect-modal")).GetCssValue("display")); + Browser.Equal("none", () => Browser.Exists(By.Id("components-reconnect-modal")).GetCssValue("display")); + Browser.Exists(By.Id("increment-0")).Click(); + Browser.Equal("3", () => Browser.Exists(By.Id("count-0")).Text); + } + + [Fact] + public void RootComponentOperation_Add_WaitsUntilReconnection() + { + Navigate($"{ServerPathBase}/streaming-interactivity"); + + Browser.Equal("Not streaming", () => Browser.FindElement(By.Id("status")).Text); + + Browser.Click(By.Id("add-server-counter-prerendered-link")); + Browser.Equal("True", () => Browser.FindElement(By.Id("is-interactive-0")).Text); + + var javascript = (IJavaScriptExecutor)Browser; + javascript.ExecuteScript("Blazor._internal.forceCloseConnection()"); + + // Remove the reconnection modal so we can interact with the page while reconnection is in progress + // We'll store the modal on the 'window' object so we can read its state without it needing + // to be in the DOM + var reconnectModal = Browser.Exists(By.Id("components-reconnect-modal")); + RemoveReconnectModal(javascript, reconnectModal); + + // Add a new component via enhanced update while reconnection is in progress + Browser.Click(By.Id("add-server-counter-prerendered-link")); + + // Assert that the component was added + Browser.Equal("False", () => Browser.FindElement(By.Id("is-interactive-1")).Text); + + // Assert that we go from a disconnected to reconnected state + Browser.Equal("block", () => GetRemovedReconnectModalDisplay(javascript)); + Browser.Equal("none", () => GetRemovedReconnectModalDisplay(javascript)); + + // Assert that we become interactive when reconnection completes + Browser.Equal("True", () => Browser.FindElement(By.Id("is-interactive-1")).Text); + } + + [Fact] + public void RootComponentOperation_Update_WaitsUntilReconnection() + { + Navigate($"{ServerPathBase}/streaming-interactivity"); + + Browser.Equal("Not streaming", () => Browser.FindElement(By.Id("status")).Text); + + Browser.Click(By.Id("add-server-counter-prerendered-link")); + Browser.Equal("True", () => Browser.FindElement(By.Id("is-interactive-0")).Text); + + var javascript = (IJavaScriptExecutor)Browser; + javascript.ExecuteScript("Blazor._internal.forceCloseConnection()"); + + // Remove the reconnection modal so we can interact with the page while reconnection is in progress + // We'll store the modal on the 'window' object so we can read its state without it needing + // to be in the DOM + var reconnectModal = Browser.Exists(By.Id("components-reconnect-modal")); + RemoveReconnectModal(javascript, reconnectModal); + + // Update the existing counter's increment amount via enhanced update while reconnection is in progress + Browser.Click(By.Id("update-counter-link-0")); + + // Assert that we go from a disconnected to reconnected state + Browser.Equal("block", () => GetRemovedReconnectModalDisplay(javascript)); + Browser.Equal("none", () => GetRemovedReconnectModalDisplay(javascript)); + + // Assert that the increment amount was updated after the browser reconnected + Browser.Equal("2", () => Browser.FindElement(By.Id("increment-amount-0")).Text); + Browser.Click(By.Id("increment-0")); + Browser.Equal("2", () => Browser.FindElement(By.Id("count-0")).Text); + } + + [Fact] + public void RootComponentOperation_Remove_WaitsUntilReconnection() + { + Navigate($"{ServerPathBase}/streaming-interactivity"); + + Browser.Equal("Not streaming", () => Browser.FindElement(By.Id("status")).Text); + + Browser.Click(By.Id("add-server-counter-prerendered-link")); + Browser.Equal("True", () => Browser.FindElement(By.Id("is-interactive-0")).Text); + + Browser.Click(By.Id("add-server-counter-prerendered-link")); + Browser.Equal("True", () => Browser.FindElement(By.Id("is-interactive-1")).Text); + + var javascript = (IJavaScriptExecutor)Browser; + javascript.ExecuteScript("Blazor._internal.forceCloseConnection()"); + + // Remove the reconnection modal so we can interact with the page while reconnection is in progress + // We'll store the modal on the 'window' object so we can read its state without it needing + // to be in the DOM + var reconnectModal = Browser.Exists(By.Id("components-reconnect-modal")); + RemoveReconnectModal(javascript, reconnectModal); + + // Remove the counter via enhanced update while reconnection is in progress + Browser.Click(By.Id("remove-counter-link-1")); + Browser.DoesNotExist(By.Id("remove-counter-link-1")); + + AssertBrowserLogDoesNotContainMessage($"Counter 1 was disposed"); + + // Assert that we go from a disconnected to reconnected state + Browser.Equal("block", () => GetRemovedReconnectModalDisplay(javascript)); + Browser.Equal("none", () => GetRemovedReconnectModalDisplay(javascript)); + + // Assert that the component was disposed after the browser reconnected + AssertBrowserLogContainsMessage($"Counter 1 was disposed"); + } + + private static void RemoveReconnectModal(IJavaScriptExecutor javascript, IWebElement reconnectModal) + { + javascript.ExecuteScript(""" + window.reconnectModal = arguments[0]; + window.reconnectModal.remove(); + """, reconnectModal); + } + + private static string GetRemovedReconnectModalDisplay(IJavaScriptExecutor javascript) + { + return (string)javascript.ExecuteScript("return window.reconnectModal.style.display;"); + } + + private void AssertBrowserLogContainsMessage(string message) + => Browser.True(() => DoesBrowserLogContainMessage(message)); + + private void AssertBrowserLogDoesNotContainMessage(string message) + => Browser.False(() => DoesBrowserLogContainMessage(message)); + + private bool DoesBrowserLogContainMessage(string message) + { + var entries = Browser.Manage().Logs.GetLog(LogType.Browser); + return entries.Any(entry => entry.Message.Contains(message)); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -82,7 +82,7 @@ // Specifying text/html is to make the enhanced nav outcomes more similar to non-enhanced nav. // For example, the default error middleware will only serve the error page if this content type is requested. // The blazor-enhanced-nav parameter can be used to trigger arbitrary server-side behaviors. - Assert.Contains("accept: text/html;blazor-enhanced-nav=on", allHeaders); + Assert.Contains("accept: text/html; blazor-enhanced-nav=on", allHeaders); } [Fact] @@ -93,7 +93,7 @@ var originalH1Elem = Browser.Exists(By.TagName("h1")); Browser.Equal("Hello", () => originalH1Elem.Text); - Browser.Exists(By.TagName("nav")).FindElement(By.LinkText("Other (no enhanced nav)")).Click(); + Browser.Exists(By.TagName("nav")).FindElement(By.Id("not-enhanced-nav-link")).Click(); // Check we got there, but we did *not* retain the

    element Browser.Equal("Other", () => Browser.Exists(By.TagName("h1")).Text); @@ -405,6 +405,155 @@ Assert.Equal("undefined", Browser.ExecuteJavaScript("return typeof Blazor")); // Blazor JS is NOT loaded } + [Theory] + [InlineData("server")] + [InlineData("wasm")] + public void LocationChangedEventGetsInvokedOnEnhancedNavigation_OnlyServerOrWebAssembly(string renderMode) + { + Navigate($"{ServerPathBase}/nav"); + Browser.Equal("Hello", () => Browser.Exists(By.TagName("h1")).Text); + + Browser.Exists(By.TagName("nav")).FindElement(By.LinkText($"LocationChanged/LocationChanging event ({renderMode})")).Click(); + Browser.Equal("Page with location changed components", () => Browser.Exists(By.TagName("h1")).Text); + Browser.Equal("0", () => Browser.Exists(By.Id($"location-changed-count-{renderMode}")).Text); + + Browser.Exists(By.Id($"update-query-string-{renderMode}")).Click(); + + Browser.Equal("1", () => Browser.Exists(By.Id($"location-changed-count-{renderMode}")).Text); + } + + [Theory] + [InlineData("server")] + [InlineData("wasm")] + public void LocationChangedEventGetsInvokedOnEnhancedNavigation_BothServerAndWebAssembly(string runtimeThatInvokedNavigation) + { + Navigate($"{ServerPathBase}/nav"); + Browser.Equal("Hello", () => Browser.Exists(By.TagName("h1")).Text); + + Browser.Exists(By.TagName("nav")).FindElement(By.LinkText("LocationChanged/LocationChanging event (server-and-wasm)")).Click(); + Browser.Equal("Page with location changed components", () => Browser.Exists(By.TagName("h1")).Text); + Browser.Equal("0", () => Browser.Exists(By.Id("location-changed-count-server")).Text); + Browser.Equal("0", () => Browser.Exists(By.Id("location-changed-count-wasm")).Text); + + Browser.Exists(By.Id($"update-query-string-{runtimeThatInvokedNavigation}")).Click(); + + // LocationChanged event gets invoked for both interactive runtimes + Browser.Equal("1", () => Browser.Exists(By.Id("location-changed-count-server")).Text); + Browser.Equal("1", () => Browser.Exists(By.Id("location-changed-count-wasm")).Text); + } + + [Theory] + [InlineData("server")] + [InlineData("wasm")] + public void NavigationManagerUriGetsUpdatedOnEnhancedNavigation_OnlyServerOrWebAssembly(string renderMode) + { + Navigate($"{ServerPathBase}/nav"); + Browser.Equal("Hello", () => Browser.Exists(By.TagName("h1")).Text); + + Browser.Exists(By.TagName("nav")).FindElement(By.LinkText($"LocationChanged/LocationChanging event ({renderMode})")).Click(); + Browser.Equal("Page with location changed components", () => Browser.Exists(By.TagName("h1")).Text); + Assert.EndsWith($"/nav/location-changed/{renderMode}", Browser.Exists(By.Id($"nav-uri-{renderMode}")).Text); + + Browser.Exists(By.Id($"update-query-string-{renderMode}")).Click(); + + Assert.EndsWith($"/nav/location-changed/{renderMode}?query=1", Browser.Exists(By.Id($"nav-uri-{renderMode}")).Text); + } + + [Theory] + [InlineData("server")] + [InlineData("wasm")] + public void NavigationManagerUriGetsUpdatedOnEnhancedNavigation_BothServerAndWebAssembly(string runtimeThatInvokedNavigation) + { + Navigate($"{ServerPathBase}/nav"); + Browser.Equal("Hello", () => Browser.Exists(By.TagName("h1")).Text); + + Browser.Exists(By.TagName("nav")).FindElement(By.LinkText("LocationChanged/LocationChanging event (server-and-wasm)")).Click(); + Browser.Equal("Page with location changed components", () => Browser.Exists(By.TagName("h1")).Text); + Assert.EndsWith("/nav/location-changed/server-and-wasm", Browser.Exists(By.Id("nav-uri-server")).Text); + Assert.EndsWith("/nav/location-changed/server-and-wasm", Browser.Exists(By.Id("nav-uri-wasm")).Text); + + Browser.Exists(By.Id($"update-query-string-{runtimeThatInvokedNavigation}")).Click(); + + Assert.EndsWith($"/nav/location-changed/server-and-wasm?query=1", Browser.Exists(By.Id($"nav-uri-server")).Text); + Assert.EndsWith($"/nav/location-changed/server-and-wasm?query=1", Browser.Exists(By.Id($"nav-uri-wasm")).Text); + } + + [Theory] + [InlineData("server")] + [InlineData("wasm")] + public void SupplyParameterFromQueryGetsUpdatedOnEnhancedNavigation_OnlyServerOrWebAssembly(string renderMode) + { + Navigate($"{ServerPathBase}/nav"); + Browser.Equal("Hello", () => Browser.Exists(By.TagName("h1")).Text); + + Browser.Exists(By.TagName("nav")).FindElement(By.LinkText($"LocationChanged/LocationChanging event ({renderMode})")).Click(); + Browser.Equal("Page with location changed components", () => Browser.Exists(By.TagName("h1")).Text); + + Browser.Exists(By.Id($"update-query-string-{renderMode}")).Click(); + Browser.Equal("1", () => Browser.Exists(By.Id($"query-{renderMode}")).Text); + + Browser.Exists(By.Id($"update-query-string-{renderMode}")).Click(); + Browser.Equal("2", () => Browser.Exists(By.Id($"query-{renderMode}")).Text); + } + + [Theory] + [InlineData("server")] + [InlineData("wasm")] + public void SupplyParameterFromQueryGetsUpdatedOnEnhancedNavigation_BothServerAndWebAssembly(string runtimeThatInvokedNavigation) + { + Navigate($"{ServerPathBase}/nav"); + Browser.Equal("Hello", () => Browser.Exists(By.TagName("h1")).Text); + + Browser.Exists(By.TagName("nav")).FindElement(By.LinkText("LocationChanged/LocationChanging event (server-and-wasm)")).Click(); + Browser.Equal("Page with location changed components", () => Browser.Exists(By.TagName("h1")).Text); + + Browser.Exists(By.Id($"update-query-string-{runtimeThatInvokedNavigation}")).Click(); + Browser.Equal("1", () => Browser.Exists(By.Id("query-server")).Text); + Browser.Equal("1", () => Browser.Exists(By.Id("query-wasm")).Text); + + Browser.Exists(By.Id($"update-query-string-{runtimeThatInvokedNavigation}")).Click(); + Browser.Equal("2", () => Browser.Exists(By.Id("query-server")).Text); + Browser.Equal("2", () => Browser.Exists(By.Id("query-wasm")).Text); + } + + [Theory] + [InlineData("server")] + [InlineData("wasm")] + public void LocationChangingEventGetsInvokedOnEnhancedNavigation_OnlyServerOrWebAssembly(string renderMode) + { + Navigate($"{ServerPathBase}/nav"); + Browser.Equal("Hello", () => Browser.Exists(By.TagName("h1")).Text); + + Browser.Exists(By.TagName("nav")).FindElement(By.LinkText($"LocationChanged/LocationChanging event ({renderMode})")).Click(); + Browser.Equal("Page with location changed components", () => Browser.Exists(By.TagName("h1")).Text); + Browser.Equal("0", () => Browser.Exists(By.Id($"location-changing-count-{renderMode}")).Text); + + Browser.Exists(By.Id($"update-query-string-{renderMode}")).Click(); + + Browser.Equal("1", () => Browser.Exists(By.Id($"location-changing-count-{renderMode}")).Text); + } + + [Theory] + [InlineData("server")] + [InlineData("wasm")] + public void LocationChangingEventGetsInvokedOnEnhancedNavigationOnlyForRuntimeThatInvokedNavigation(string runtimeThatInvokedNavigation) + { + Navigate($"{ServerPathBase}/nav"); + Browser.Equal("Hello", () => Browser.Exists(By.TagName("h1")).Text); + + Browser.Exists(By.TagName("nav")).FindElement(By.LinkText("LocationChanged/LocationChanging event (server-and-wasm)")).Click(); + Browser.Equal("Page with location changed components", () => Browser.Exists(By.TagName("h1")).Text); + Browser.Equal("0", () => Browser.Exists(By.Id("location-changing-count-server")).Text); + Browser.Equal("0", () => Browser.Exists(By.Id("location-changing-count-wasm")).Text); + + Browser.Exists(By.Id($"update-query-string-{runtimeThatInvokedNavigation}")).Click(); + + // LocationChanging event gets invoked only for the interactive runtime that invoked navigation + var anotherRuntime = runtimeThatInvokedNavigation == "server" ? "wasm" : "server"; + Browser.Equal("1", () => Browser.Exists(By.Id($"location-changing-count-{runtimeThatInvokedNavigation}")).Text); + Browser.Equal("0", () => Browser.Exists(By.Id($"location-changing-count-{anotherRuntime}")).Text); + } + private void AssertEnhancedUpdateCountEquals(long count) => Browser.Equal(count, () => ((IJavaScriptExecutor)Browser).ExecuteScript("return window.enhancedPageUpdateCount;")); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -306,17 +306,15 @@ public void CanBindComplexTypeToDefaultForm(bool suppressEnhancedNavigation) { var url = "forms/default-form-bound-complextype-parameter"; - var expectedTarget = GetExpectedTarget(this, null, url); + var expectedAction = GetExpectedActionValue(this, url); SuppressEnhancedNavigation(suppressEnhancedNavigation); GoTo(url); Browser.Exists(By.Id("ready")); var form = Browser.Exists(By.CssSelector("form")); - var formTarget = form.GetAttribute("action"); - var actionValue = form.GetDomAttribute("action"); - Assert.Equal(expectedTarget, formTarget); - Assert.Null(actionValue); + var actionValue = ReadFormActionAttribute(form); + Assert.Equal(expectedAction, actionValue); var name = Browser.Exists(By.CssSelector("""input[name="Model.Name"]""")); name.SendKeys("John"); @@ -333,7 +331,7 @@ { // Verify the same form element is still in the page // We wouldn't be allowed to read the attribute if the element is stale - Assert.Equal(expectedTarget, form.GetAttribute("action")); + Assert.NotEmpty(form.GetAttribute("action")); } } @@ -343,17 +341,15 @@ public void CanBreakFormIntoMultipleComponents(bool suppressEnhancedNavigation) { var url = "forms/default-form-bound-complextype-multiple-components"; - var expectedTarget = GetExpectedTarget(this, null, url); + var expectedAction = GetExpectedActionValue(this, url); SuppressEnhancedNavigation(suppressEnhancedNavigation); GoTo(url); Browser.Exists(By.Id("ready")); var form = Browser.Exists(By.CssSelector("form")); - var formTarget = form.GetAttribute("action"); - var actionValue = form.GetDomAttribute("action"); - Assert.Equal(expectedTarget, formTarget); - Assert.Null(actionValue); + var actionValue = ReadFormActionAttribute(form); + Assert.Equal(expectedAction, actionValue); var name = Browser.Exists(By.CssSelector("""input[name="Model.Name"]""")); name.SendKeys("John"); @@ -392,7 +388,7 @@ { // Verify the same form element is still in the page // We wouldn't be allowed to read the attribute if the element is stale - Assert.Equal(expectedTarget, form.GetAttribute("action")); + Assert.NotEmpty(form.GetAttribute("action")); } } @@ -402,17 +398,15 @@ public void CanBreakFormIntoMultipleComponentsDisplaysErrorsCorrectly(bool suppressEnhancedNavigation) { var url = "forms/default-form-bound-complextype-multiple-components"; - var expectedTarget = GetExpectedTarget(this, null, url); + var expectedAction = GetExpectedActionValue(this, url); SuppressEnhancedNavigation(suppressEnhancedNavigation); GoTo(url); Browser.Exists(By.Id("ready")); var form = Browser.Exists(By.CssSelector("form")); - var formTarget = form.GetAttribute("action"); - var actionValue = form.GetDomAttribute("action"); - Assert.Equal(expectedTarget, formTarget); - Assert.Null(actionValue); + var actionValue = ReadFormActionAttribute(form); + Assert.Equal(expectedAction, actionValue); var name = Browser.Exists(By.CssSelector("""input[name="Model.Name"]""")); name.SendKeys("John"); @@ -445,7 +439,7 @@ { // Verify the same form element is still in the page // We wouldn't be allowed to read the attribute if the element is stale - Assert.Equal(expectedTarget, form.GetAttribute("action")); + Assert.NotEmpty(form.GetAttribute("action")); } } @@ -455,17 +449,15 @@ public void CanDisplayBindingErrorsComplexTypeToDefaultForm(bool suppressEnhancedNavigation) { var url = "forms/default-form-bound-complextype-parameter"; - var expectedTarget = GetExpectedTarget(this, null, url); + var expectedAction = GetExpectedActionValue(this, url); SuppressEnhancedNavigation(suppressEnhancedNavigation); GoTo(url); Browser.Exists(By.Id("ready")); var form = Browser.Exists(By.CssSelector("form")); - var formTarget = form.GetAttribute("action"); - var actionValue = form.GetDomAttribute("action"); - Assert.Equal(expectedTarget, formTarget); - Assert.Null(actionValue); + var actionValue = ReadFormActionAttribute(form); + Assert.Equal(expectedAction, actionValue); var name = Browser.Exists(By.CssSelector("""input[name="Model.Name"]""")); name.SendKeys("John"); @@ -485,7 +477,7 @@ { // Verify the same form element is still in the page // We wouldn't be allowed to read the attribute if the element is stale - Assert.Equal(expectedTarget, form.GetAttribute("action")); + Assert.NotEmpty(form.GetAttribute("action")); } } @@ -495,17 +487,15 @@ public void CanBindDictionaryToDefaultForm(bool suppressEnhancedNavigation) { var url = "forms/default-form-bound-dictionary-parameter"; - var expectedTarget = GetExpectedTarget(this, null, url); + var expectedAction = GetExpectedActionValue(this, url); SuppressEnhancedNavigation(suppressEnhancedNavigation); GoTo(url); Browser.Exists(By.Id("ready")); var form = Browser.Exists(By.CssSelector("form")); - var formTarget = form.GetAttribute("action"); - var actionValue = form.GetDomAttribute("action"); - Assert.Equal(expectedTarget, formTarget); - Assert.Null(actionValue); + var actionValue = ReadFormActionAttribute(form); + Assert.Equal(expectedAction, actionValue); var name = Browser.Exists(By.CssSelector("""input[name="Model[Name]"]""")); name.SendKeys("John"); @@ -522,7 +512,7 @@ { // Verify the same form element is still in the page // We wouldn't be allowed to read the attribute if the element is stale - Assert.Equal(expectedTarget, form.GetAttribute("action")); + Assert.NotEmpty(form.GetAttribute("action")); } } @@ -532,17 +522,15 @@ public void CanDisplayBindingErrorsDictionaryToDefaultForm(bool suppressEnhancedNavigation) { var url = "forms/default-form-bound-dictionary-parameter-errors"; - var expectedTarget = GetExpectedTarget(this, null, url); + var expectedAction = GetExpectedActionValue(this, url); SuppressEnhancedNavigation(suppressEnhancedNavigation); GoTo(url); Browser.Exists(By.Id("ready")); var form = Browser.Exists(By.CssSelector("form")); - var formTarget = form.GetAttribute("action"); - var actionValue = form.GetDomAttribute("action"); - Assert.Equal(expectedTarget, formTarget); - Assert.Null(actionValue); + var actionValue = ReadFormActionAttribute(form); + Assert.Equal(expectedAction, actionValue); var name = Browser.Exists(By.CssSelector("""input[name="Model[Name]"]""")); ((IJavaScriptExecutor)Browser).ExecuteScript("arguments[0].setAttribute('value', 'name')", name); @@ -563,7 +551,7 @@ { // Verify the same form element is still in the page // We wouldn't be allowed to read the attribute if the element is stale - Assert.Equal(expectedTarget, form.GetAttribute("action")); + Assert.NotEmpty(form.GetAttribute("action")); } } @@ -573,17 +561,15 @@ public void CanBindCollectionsToDefaultForm(bool suppressEnhancedNavigation) { var url = "forms/default-form-bound-collection-parameter"; - var expectedTarget = GetExpectedTarget(this, null, url); + var expectedAction = GetExpectedActionValue(this, url); SuppressEnhancedNavigation(suppressEnhancedNavigation); GoTo(url); Browser.Exists(By.Id("ready")); var form = Browser.Exists(By.CssSelector("form")); - var formTarget = form.GetAttribute("action"); - var actionValue = form.GetDomAttribute("action"); - Assert.Equal(expectedTarget, formTarget); - Assert.Null(actionValue); + var actionValue = ReadFormActionAttribute(form); + Assert.Equal(expectedAction, actionValue); for (var i = 0; i < 2; i++) { @@ -609,7 +595,7 @@ { // Verify the same form element is still in the page // We wouldn't be allowed to read the attribute if the element is stale - Assert.Equal(expectedTarget, form.GetAttribute("action")); + Assert.NotEmpty(form.GetAttribute("action")); } } @@ -619,17 +605,15 @@ public void CanDisplayBindingErrorsCollectionsToDefaultForm(bool suppressEnhancedNavigation) { var url = "forms/default-form-bound-collection-parameter"; - var expectedTarget = GetExpectedTarget(this, null, url); + var expectedAction = GetExpectedActionValue(this, url); SuppressEnhancedNavigation(suppressEnhancedNavigation); GoTo(url); Browser.Exists(By.Id("ready")); var form = Browser.Exists(By.CssSelector("form")); - var formTarget = form.GetAttribute("action"); - var actionValue = form.GetDomAttribute("action"); - Assert.Equal(expectedTarget, formTarget); - Assert.Null(actionValue); + var actionValue = ReadFormActionAttribute(form); + Assert.Equal(expectedAction, actionValue); for (var i = 0; i < 2; i++) { @@ -660,7 +644,7 @@ { // Verify the same form element is still in the page // We wouldn't be allowed to read the attribute if the element is stale - Assert.Equal(expectedTarget, form.GetAttribute("action")); + Assert.NotEmpty(form.GetAttribute("action")); } } @@ -840,12 +824,13 @@ [Fact] public async Task CanPostFormsWithStreamingRenderingAsync() { - GoTo("forms/streaming-rendering/CanPostFormsWithStreamingRendering"); - + const string url = "forms/streaming-rendering/CanPostFormsWithStreamingRendering"; + GoTo(url); + var expectedAction = GetExpectedActionValue(this, url); Browser.Exists(By.Id("ready")); var form = Browser.Exists(By.CssSelector("form")); - var actionValue = form.GetDomAttribute("action"); - Assert.Null(actionValue); + var actionValue = ReadFormActionAttribute(form); + Assert.Equal(expectedAction, actionValue); Browser.Click(By.Id("send")); @@ -861,12 +846,13 @@ [Fact] public async Task CanModifyTheHttpResponseDuringEventHandling() { - GoTo("forms/modify-http-context/ModifyHttpContext"); - + const string url = "forms/modify-http-context/ModifyHttpContext"; + GoTo(url); + var expectedAction = GetExpectedActionValue(this, url); Browser.Exists(By.Id("ready")); var form = Browser.Exists(By.CssSelector("form")); - var actionValue = form.GetDomAttribute("action"); - Assert.Null(actionValue); + var actionValue = ReadFormActionAttribute(form); + Assert.Equal(expectedAction, actionValue); Browser.Click(By.Id("send")); @@ -896,6 +882,32 @@ DispatchToFormCore(dispatchToForm); } + [Fact] + public void CanUseAntiforgeryTokenInWasm() + { + var dispatchToForm = new DispatchToForm(this) + { + Url = "forms/antiforgery-wasm", + FormCssSelector = "form", + InputFieldId = "Value", + SuppressEnhancedNavigation = true, + }; + DispatchToFormCore(dispatchToForm); + } + + [Fact] + public void CanUseAntiforgeryTokenWithServerInteractivity() + { + var dispatchToForm = new DispatchToForm(this) + { + Url = "forms/antiforgery-server-interactive", + FormCssSelector = "form", + InputFieldId = "value", + SuppressEnhancedNavigation = true, + }; + DispatchToFormCore(dispatchToForm); + } + [Theory] [InlineData(true)] [InlineData(false)] @@ -978,12 +990,13 @@ [Fact] public async Task CanHandleFormPostNonStreamingRenderingAsyncHandler() { - GoTo("forms/non-streaming-async-form-handler/CanHandleFormPostNonStreamingRenderingAsyncHandler"); - + const string url = "forms/non-streaming-async-form-handler/CanHandleFormPostNonStreamingRenderingAsyncHandler"; + GoTo(url); + var expectedAction = GetExpectedActionValue(this, url); Browser.Exists(By.Id("ready")); var form = Browser.Exists(By.CssSelector("form")); - var actionValue = form.GetDomAttribute("action"); - Assert.Null(actionValue); + var actionValue = ReadFormActionAttribute(form); + Assert.Equal(expectedAction, actionValue); Browser.Click(By.Id("send")); @@ -1004,7 +1017,7 @@ public void HandleErrorsOutsideErrorBoundary_OnInitialRender(bool suppressEnhancedNavigation, bool enableStreaming) { SuppressEnhancedNavigation(suppressEnhancedNavigation); - GoTo($"forms/error-outside-error-boundary{( enableStreaming ? "-streaming" : "" )}"); + GoTo($"forms/error-outside-error-boundary{(enableStreaming ? "-streaming" : "")}"); Browser.Exists(By.LinkText("Throw during initial render")).Click(); AssertHasInternalServerError(suppressEnhancedNavigation); @@ -1101,6 +1114,20 @@ Browser.True(() => Browser.Url.EndsWith("/nav", StringComparison.Ordinal)); } + [Fact] + public void CanPostRedirectGet_OnGoingRequest() + { + GoTo($"forms/form-posted-while-enhanced-nav-in-progress"); + + Browser.Exists(By.Id("not-ending")).Click(); + Browser.True(() => Browser.Url.EndsWith("forms/endpoint-that-never-finishes-rendering", StringComparison.Ordinal)); + Browser.Exists(By.Id("send")).Click(); + Browser.Exists(By.Id("pass")); + Browser.True(() => Browser.Url.EndsWith("forms/form-posted-while-enhanced-nav-in-progress", StringComparison.Ordinal)); + Browser.Navigate().Back(); + Browser.True(() => Browser.Url.EndsWith("forms/endpoint-that-never-finishes-rendering", StringComparison.Ordinal)); + } + [Theory] [InlineData(false, false)] [InlineData(false, true)] @@ -1257,6 +1284,64 @@ Assert.Equal("Total: 7", Browser.Exists(By.Id("form-collection")).Text); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CanUseFormWithMethodGet(bool suppressEnhancedNavigation) + { + SuppressEnhancedNavigation(suppressEnhancedNavigation); + GoTo("forms/method-get"); + Browser.Equal("Form with method=get", () => Browser.FindElement(By.TagName("h2")).Text); + + // Validate initial state + var stringInput = Browser.FindElement(By.Id("mystring")); + var boolInput = Browser.FindElement(By.Id("mybool")); + Browser.Equal("Initial value", () => stringInput.GetDomProperty("value")); + Browser.Equal("False", () => boolInput.GetDomProperty("checked")); + + // Edit and submit the form; check it worked + stringInput.Clear(); + stringInput.SendKeys("Edited value"); + boolInput.Click(); + Browser.FindElement(By.Id("submit-get-form")).Click(); + AssertUiState("Edited value", true); + Browser.Contains($"MyString=Edited+value", () => Browser.Url); + Browser.Contains($"MyBool=True", () => Browser.Url); + + // Check 'back' correctly gets us to the previous state + Browser.Navigate().Back(); + AssertUiState("Initial value", false); + Browser.False(() => Browser.Url.Contains("MyString")); + Browser.False(() => Browser.Url.Contains("MyBool")); + + // Check 'forward' correctly recreates the edited state + Browser.Navigate().Forward(); + AssertUiState("Edited value", true); + Browser.Contains($"MyString=Edited+value", () => Browser.Url); + Browser.Contains($"MyBool=True", () => Browser.Url); + + void AssertUiState(string expectedStringValue, bool expectedBoolValue) + { + Browser.Equal(expectedStringValue, () => Browser.FindElement(By.Id("mystring-value")).Text); + Browser.Equal(expectedBoolValue.ToString(), () => Browser.FindElement(By.Id("mybool-value")).Text); + + // If we're not suppressing, we'll keep referencing the same elements to show they were preserved + if (suppressEnhancedNavigation) + { + stringInput = Browser.FindElement(By.Id("mystring")); + boolInput = Browser.FindElement(By.Id("mybool")); + } + + Browser.Equal(expectedStringValue, () => stringInput.GetDomProperty("value")); + Browser.Equal(expectedBoolValue.ToString(), () => boolInput.GetDomProperty("checked")); + } + } + + // Can't just use GetAttribute or GetDomAttribute because they both auto-resolve it + // to an absolute URL. We want to be able to assert about the attribute's literal value. + private string ReadFormActionAttribute(IWebElement form) + => (string)((IJavaScriptExecutor)Browser).ExecuteScript("return arguments[0].getAttribute('action')", form); + private void DispatchToFormCore(DispatchToForm dispatch) { SuppressEnhancedNavigation(dispatch.SuppressEnhancedNavigation); @@ -1379,8 +1464,8 @@ public bool FormIsEnhanced { get; internal set; } = true; // Default to true because that's the case for almost all test cases } - private string GetExpectedTarget(FormWithParentBindingContextTest test, string expectedActionValue, string url) - => $"{new Uri(test._serverFixture.RootUri, test.ServerPathBase)}/{expectedActionValue ?? url}"; + private string GetExpectedActionValue(FormWithParentBindingContextTest test, string expectedActionValue) + => $"{test.ServerPathBase}/{expectedActionValue}"; private void GoTo(string relativePath) { diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/InteractivityTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/InteractivityTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/InteractivityTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/InteractivityTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -229,7 +229,7 @@ { Browser.Click(By.Id(addCounterLinkIds[i])); Browser.Equal("True", () => Browser.FindElement(By.Id($"is-interactive-{i}")).Text); - + Browser.Click(By.Id($"increment-{i}")); Browser.Equal("1", () => Browser.FindElement(By.Id($"count-{i}")).Text); } @@ -285,7 +285,7 @@ Browser.Equal($"{i + 1}", () => Browser.FindElement(By.Id($"count-{i}")).Text); } - AssertBrowserLogDoesNotContainErrors(); + AssertBrowserLogDoesNotContainErrors(entry => !entry.Message.Contains("Initializer")); } [Theory] @@ -338,6 +338,15 @@ AssertBrowserLogDoesNotContainErrors(); } + private void EnableClassicInitializers(IWebDriver browser, bool skipNavigation) + { + if (!skipNavigation) + { + browser.Navigate().GoToUrl($"{new Uri(_serverFixture.RootUri, ServerPathBase)}/"); + } + ((IJavaScriptExecutor)browser).ExecuteScript("sessionStorage.setItem('enable-classic-initializers', 'true')"); + } + [Theory] [MemberData(nameof(AddCounterLinkSequences))] public void InteractiveRootComponents_CanReceiveSsrParameterUpdates_FromEnhancedNavigation(string[] addCounterLinkIds) @@ -402,6 +411,103 @@ [Theory] [MemberData(nameof(AddCounterLinkSequences))] + public void InteractiveRootComponents_GetReinitialized_WhenNoKeyIsProvided_AfterReceivingSsrParameterUpdates_FromEnhancedNavigation(string[] addCounterLinkIds) + { + Navigate($"{ServerPathBase}/streaming-interactivity"); + + Browser.Equal("Not streaming", () => Browser.FindElement(By.Id("status")).Text); + Browser.Click(By.Id("disable-keys-link")); + Browser.Exists(By.Id("enable-keys-link")); + + for (var i = 0; i < addCounterLinkIds.Length; i++) + { + Browser.Click(By.Id(addCounterLinkIds[i])); + Browser.Equal("True", () => Browser.FindElement(By.Id($"is-interactive-{i}")).Text); + Browser.Click(By.Id($"increment-{i}")); + Browser.Equal("1", () => Browser.FindElement(By.Id($"count-{i}")).Text); + } + + for (var i = 0; i < addCounterLinkIds.Length; i++) + { + Browser.Click(By.Id($"update-counter-link-{i}")); + Browser.Equal("2", () => Browser.FindElement(By.Id($"increment-amount-{i}")).Text); + Browser.Equal("0", () => Browser.FindElement(By.Id($"count-{i}")).Text); // Resets back to 0 because the parameter changed + + // Ensure that interactivity still works, and the correct parameters have been supplied. + Browser.Click(By.Id($"increment-{i}")); + Browser.Equal("2", () => Browser.FindElement(By.Id($"count-{i}")).Text); + + for (var j = 0; j < addCounterLinkIds.Length; j++) + { + if (j == i) + { + continue; + } + + // Ensure that other components didn't get reset; their parameters did not change. + Browser.NotEqual("0", () => Browser.FindElement(By.Id($"count-{j}")).Text); + } + } + + AssertBrowserLogDoesNotContainErrors(); + } + + [Theory] + [MemberData(nameof(AddCounterLinkSequences))] + public void InteractiveRootComponents_GetReinitialized_WhenNoKeyIsProvided_AfterReceivingSsrParameterUpdates_FromStreamingRenderingUpdate(string[] addCounterLinkIds) + { + Navigate($"{ServerPathBase}/streaming-interactivity"); + + Browser.Equal("Not streaming", () => Browser.FindElement(By.Id("status")).Text); + Browser.Click(By.Id("disable-keys-link")); + Browser.Exists(By.Id("enable-keys-link")); + + // Components don't become interactive during streaming rendering, so we need to + // add then via enhanced navigation first + for (var i = 0; i < addCounterLinkIds.Length; i++) + { + Browser.Click(By.Id(addCounterLinkIds[i])); + Browser.Equal("True", () => Browser.FindElement(By.Id($"is-interactive-{i}")).Text); + Browser.Click(By.Id($"increment-{i}")); + Browser.Equal("1", () => Browser.FindElement(By.Id($"count-{i}")).Text); + } + + Browser.Click(By.Id("start-streaming-link")); + Browser.Equal("Streaming", () => Browser.FindElement(By.Id("status")).Text); + + for (var i = 0; i < addCounterLinkIds.Length; i++) + { + Browser.Click(By.Id($"update-counter-link-{i}")); + Browser.Equal("2", () => Browser.FindElement(By.Id($"increment-amount-{i}")).Text); + Browser.Equal("0", () => Browser.FindElement(By.Id($"count-{i}")).Text); // Resets back to 0 because the parameter changed + + // Ensure that interactivity still works, and the correct parameters have been supplied. + // Note that while SSR'd components don't become interactive during stream rendering, + // this streaming update replaced an already-interactive component. To ensure that supplying + // unchanged parameters behaves the same way as supplying updated parameters, we + // check that the component is still interactive. + Browser.Click(By.Id($"increment-{i}")); + Browser.Equal("2", () => Browser.FindElement(By.Id($"count-{i}")).Text); + + for (var j = 0; j < addCounterLinkIds.Length; j++) + { + if (j == i) + { + continue; + } + + // Ensure that other components didn't get reset; their parameters did not change. + Browser.NotEqual("0", () => Browser.FindElement(By.Id($"count-{j}")).Text); + } + } + + Browser.Click(By.Id("stop-streaming-link")); + + AssertBrowserLogDoesNotContainErrors(); + } + + [Theory] + [MemberData(nameof(AddCounterLinkSequences))] public void InteractiveRootComponents_CanGetDisposed_FromEnhancedNavigation(string[] addCounterLinkIds) { Navigate($"{ServerPathBase}/streaming-interactivity"); @@ -1064,9 +1170,13 @@ return entries.Any(entry => entry.Message.Contains(message)); } - private void AssertBrowserLogDoesNotContainErrors() + private void AssertBrowserLogDoesNotContainErrors(Func filter = null) { var entries = Browser.Manage().Logs.GetLog(LogType.Browser); + if (filter != null) + { + entries = entries.Where(filter).ToArray().AsReadOnly(); + } Assert.DoesNotContain(entries, entry => entry.Level == LogLevel.Severe); } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/StreamingRenderingTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/StreamingRenderingTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/StreamingRenderingTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/ServerRenderingTests/StreamingRenderingTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Globalization; using System.Net.Http; +using System.Net.Http.Headers; using System.Text; +using System.Text.RegularExpressions; using Components.TestServer.RazorComponents; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; @@ -275,4 +278,42 @@ Navigate($"{ServerPathBase}/streaming-with-sections"); Browser.Equal("This is some streaming content", () => Browser.Exists(By.Id("streaming-message")).Text); } + + [Fact] + public async Task WorksWithVeryBriefStreamingDelays() + { + // First check it works in the browser + Navigate($"{ServerPathBase}/brief-streaming"); + var header = Browser.Exists(By.Id("brief-streaming")); + for (var i = 1; i < 20; i++) + { + Browser.FindElement(By.LinkText("Load this page")).Click(); + + // Keep checking the same header to show this is always enhanced nav + Assert.Equal("Brief streaming", header.Text); + + Browser.True(() => + { + var loadCount = int.Parse(Browser.FindElement(By.Id("load-count")).Text, CultureInfo.InvariantCulture); + return loadCount >= i; + }); + } + + // That's not enough to be sure it was really correct, since it might + // work in the browser even if the SSR framing is emitted in the wrong + // place depending on exactly where it was emitted. To be sure, we'll + // also validate the HTML response directly. + var url = Browser.Url; + var httpClient = new HttpClient(); + for (var i = 0; i < 100; i++) + { + // We expect to see the SSR framing marker right before the first + var req = new HttpRequestMessage(HttpMethod.Get, url); + req.Headers.Accept.Clear(); + req.Headers.Add("accept", "text/html; blazor-enhanced-nav=on"); + var response = await httpClient.SendAsync(req); + var html = await response.Content.ReadAsStringAsync(); + Assert.Matches(new Regex(@""), html); + } + } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/Tests/JsInitializersTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/Tests/JsInitializersTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/Tests/JsInitializersTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/Tests/JsInitializersTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -25,11 +25,30 @@ Navigate(ServerPathBase + "#initializer"); } + // This case is essentially the same as Blazor.web.js with 'legacy' initializers enabled by default, as Blazor.server.js and Blazor.Webassembly.js + // both need to support the legacy initializers for backwards compatibility. + // The implementation accounts for the new 'modern' initializers and prefers those over the 'legacy' ones. [Fact] public void InitializersWork() { Browser.Exists(By.Id("initializer-start")); Browser.Exists(By.Id("initializer-end")); + var expectedCallbacks = GetExpectedCallbacks(); + Browser.Equal(expectedCallbacks.Length, () => Browser.FindElements(By.CssSelector("#initializers-content > p")).Count); + foreach (var callback in expectedCallbacks) + { + Browser.Exists(By.Id(callback)); + } + } + + protected virtual string[] GetExpectedCallbacks() + { + return ["classic-before-start", + "classic-after-started", + "classic-and-modern-before-web-assembly-start", + "classic-and-modern-after-web-assembly-started", + "modern-before-web-assembly-start", + "modern-after-web-assembly-started"]; } [Fact] diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/yarn.lock dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/yarn.lock --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/E2ETest/yarn.lock 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/E2ETest/yarn.lock 1970-01-01 00:00:00.000000000 +0000 @@ -1,600 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@sindresorhus/is@^4.0.0": - version "4.6.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" - integrity sha1-PHycRuZ4/u/nouW7YJ09vWZf+z8= - -"@szmarczak/http-timer@^4.0.5": - version "4.0.6" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" - integrity sha1-tKkUu2LnwnLU5Zif5EQPgSqx2Ac= - dependencies: - defer-to-connect "^2.0.0" - -"@types/cacheable-request@^6.0.1": - version "6.0.3" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" - integrity sha1-pDCzJgRmyntcpb/XNWk7Nuep0YM= - dependencies: - "@types/http-cache-semantics" "*" - "@types/keyv" "^3.1.4" - "@types/node" "*" - "@types/responselike" "^1.0.0" - -"@types/http-cache-semantics@*": - version "4.0.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" - integrity sha1-Dqe2FJaQK5WJDcTDoRa2DLja6BI= - -"@types/keyv@^3.1.4": - version "3.1.4" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" - integrity sha1-PM2xxnUbDH5SMAvNrNW8v4+qdbY= - dependencies: - "@types/node" "*" - -"@types/node@*": - version "18.13.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/node/-/node-18.13.0.tgz#0400d1e6ce87e9d3032c19eb6c58205b0d3f7850" - integrity sha1-BADR5s6H6dMDLBnrbFggWw0/eFA= - -"@types/node@^13.1.7": - version "13.13.52" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/node/-/node-13.13.52.tgz#03c13be70b9031baaed79481c0c0cfb0045e53f7" - integrity sha1-A8E75wuQMbqu15SBwMDPsAReU/c= - -"@types/responselike@^1.0.0": - version "1.0.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" - integrity sha1-JR9P59FU0rrRJavhtCmyOv0mLik= - dependencies: - "@types/node" "*" - -adm-zip@~0.4.3: - version "0.4.16" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" - integrity sha1-z0xQj9/6sCwmnLx/RxqHXwVXA2U= - -agent-base@6: - version "6.0.2" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha1-Sf/1hXfP7j83F2/qtMIuAPhtf3c= - dependencies: - debug "4" - -arg@^4.1.0: - version "4.1.3" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha1-Jp/HrVuOQstjyJbVZmAXJhwUQIk= - -async@^2.1.2: - version "2.6.4" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" - integrity sha1-cGt/9ghGZM1+rnE/b5ZUM7VQQiE= - dependencies: - lodash "^4.17.14" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha1-6D46fj8wCzTLnYf2FfoMvzV2kO4= - -base64-js@^1.3.1: - version "1.5.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha1-GxtEAWClv3rUC2UPCVljSBkDkwo= - -bl@^4.0.3: - version "4.1.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha1-RRU1JkGCvsL7vIOmKrmM8R2fezo= - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0= - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha1-KxRqb9cugLT1XSVfNe1Zo6mkG9U= - -buffer@^5.5.0: - version "5.7.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha1-umLnwTEzBTWCGXFghRqPZI6Z7tA= - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -cacheable-lookup@^5.0.3: - version "5.0.4" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" - integrity sha1-WmuGWyxENXvj1evCpGewMnGacAU= - -cacheable-request@^7.0.2: - version "7.0.2" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" - integrity sha1-6g0LiJNkolhUdXMByhKy2nf5HSc= - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^4.0.0" - lowercase-keys "^2.0.0" - normalize-url "^6.0.1" - responselike "^2.0.0" - -clone-response@^1.0.2: - version "1.0.3" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" - integrity sha1-ryAyqkeBY5nPXwodDbkC9ReruMM= - dependencies: - mimic-response "^1.0.0" - -commander@^7.2.0: - version "7.2.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha1-o2y1fQtQHOEI5NIFWaFQo5HZerc= - -concat-map@0.0.1: - version "0.0.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha1-9zqFudXUHQRVUcF34ogtSshXKKY= - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -debug@4, debug@^4.3.1: - version "4.3.4" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha1-Exn2V5NX8jONMzfSzdSRS7XcyGU= - dependencies: - ms "2.1.2" - -decompress-response@^6.0.0: - version "6.0.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" - integrity sha1-yjh2Et234QS9FthaqwDV7PCcZvw= - dependencies: - mimic-response "^3.1.0" - -defer-to-connect@^2.0.0: - version "2.0.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" - integrity sha1-gBa9tBQ+RjK3ejRJxiNid95SBYc= - -diff@^4.0.1: - version "4.0.2" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha1-YPOuy4nV+uUgwRqhnvwruYKq3n0= - -end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha1-WuZKX0UFe682JuwU2gyl5LJDHrA= - dependencies: - once "^1.4.0" - -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= - dependencies: - pend "~1.2.0" - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha1-a+Dem+mYzhavivwkSXue6bfM2a0= - -fs-extra@^10.0.0: - version "10.1.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" - integrity sha1-Aoc8+8QITd4SfqpfmQXu8jJdGr8= - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -get-stream@^5.1.0: - version "5.2.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha1-SWaheV7lrOZecGxLe+txJX1uItM= - dependencies: - pump "^3.0.0" - -glob@^7.1.3: - version "7.2.3" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha1-uN8PuAK7+o6JvR2Ti04WV47UTys= - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -got@^11.8.2: - version "11.8.6" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" - integrity sha1-J26Cfq2Hcu3bz8lxcFkLhBgjIzo= - dependencies: - "@sindresorhus/is" "^4.0.0" - "@szmarczak/http-timer" "^4.0.5" - "@types/cacheable-request" "^6.0.1" - "@types/responselike" "^1.0.0" - cacheable-lookup "^5.0.3" - cacheable-request "^7.0.2" - decompress-response "^6.0.0" - http2-wrapper "^1.0.0-beta.5.2" - lowercase-keys "^2.0.0" - p-cancelable "^2.0.0" - responselike "^2.0.0" - -graceful-fs@^4.1.6, graceful-fs@^4.2.0: - version "4.2.10" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha1-FH06AG2kyjzhRyjHrvwofDZ9emw= - -http-cache-semantics@^4.0.0: - version "4.1.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - integrity sha1-q+AvyymFRgvwMjvmZENuw0dqbVo= - -http2-wrapper@^1.0.0-beta.5.2: - version "1.0.3" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" - integrity sha1-uPVeDB8l1OvQizsMLAeflZCACz0= - dependencies: - quick-lru "^5.1.1" - resolve-alpn "^1.0.0" - -https-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" - integrity sha1-xZ7yJKBP6LdU89sAY6Jeow0ABdY= - dependencies: - agent-base "6" - debug "4" - -ieee754@^1.1.13: - version "1.2.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha1-jrehCmP/8l0VpXsAFYbRd9Gw01I= - -inflight@^1.0.4: - version "1.0.6" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.3, inherits@^2.0.4: - version "2.0.4" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w= - -is-port-reachable@^3.0.0: - version "3.1.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-port-reachable/-/is-port-reachable-3.1.0.tgz#f6668d3bca9c36b07f737c48a8f875ab0653cd2b" - integrity sha1-9maNO8qcNrB/c3xIqPh1qwZTzSs= - -isexe@^2.0.0: - version "2.0.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha1-kziAKjDTtmBfvgYT4JQAjKjAWhM= - -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha1-vFWyY0eTxnnsZAMJTrE2mKbsCq4= - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -keyv@^4.0.0: - version "4.5.2" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/keyv/-/keyv-4.5.2.tgz#0e310ce73bf7851ec702f2eaf46ec4e3805cce56" - integrity sha1-DjEM5zv3hR7HAvLq9G7E44BczlY= - dependencies: - json-buffer "3.0.1" - -lodash.mapvalues@^4.6.0: - version "4.6.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c" - integrity sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw= - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha1-VYqlO0O2YeGSWgr9+japoQhf5Xo= - -lodash@>=4.17.21, lodash@^4.16.6, lodash@^4.17.14: - version "4.17.21" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha1-Z5WRxWTDv/quhFTPCz3zcMPWkRw= - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha1-JgPni3tLAAbLyi+8yKMgJVislHk= - -make-error@^1.1.1: - version "1.3.6" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha1-LrLjfqm2fEiR9oShOUeZr0hM96I= - -mimic-response@^1.0.0: - version "1.0.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha1-SSNTiHju9CBjy4o+OweYeBSHqxs= - -mimic-response@^3.1.0: - version "3.1.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" - integrity sha1-LR1Zr5wbEpgVrMwsRqAipc4fo8k= - -minimatch@^3.1.1: - version "3.1.2" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha1-Gc0ZS/0+Qo8EmnCBfAONiatL41s= - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.5: - version "1.2.8" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha1-waRk52kzAuCCoHXO4MBXdBrEdyw= - -mkdirp@^1.0.4: - version "1.0.4" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha1-PrXtYmInVteaXw4qIh3+utdcL34= - -ms@2.1.2: - version "2.1.2" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk= - -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha1-QNCIW1Nd7/4/MUe+yHfQX+TFZoo= - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -p-cancelable@^2.0.0: - version "2.1.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" - integrity sha1-qrf71BZYL6MqPbSYWcEiSHxe0s8= - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^3.1.0: - version "3.1.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha1-WB9q3mWMu6ZaDTOA3ndTKVBU83U= - -pend@~1.2.0: - version "1.2.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= - -progress@2.0.3: - version "2.0.3" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha1-foz42PW48jnBvGi+tOt4Vn1XLvg= - -pump@^3.0.0: - version "3.0.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha1-tKIRaBW94vTh6mAjVOjHVWUQemQ= - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -quick-lru@^5.1.1: - version "5.1.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" - integrity sha1-NmST5rPkKjpoheLpnRj4D7eoyTI= - -readable-stream@^3.1.1, readable-stream@^3.4.0: - version "3.6.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha1-M3u9o63AcGvT4CRCaihtS0sskZg= - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -resolve-alpn@^1.0.0: - version "1.2.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" - integrity sha1-t629rDVGqq7CC0Xn2CZZJwcnJvk= - -responselike@^2.0.0: - version "2.0.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" - integrity sha1-mgvI/cJS8/scymiwFlkQWboUIrw= - dependencies: - lowercase-keys "^2.0.0" - -rimraf@^2.5.4: - version "2.7.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha1-NXl/E6f9rcVmFCwp1PB8ytSD4+w= - dependencies: - glob "^7.1.3" - -safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY= - -sauce-connect-launcher@^1.3.1: - version "1.3.2" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/sauce-connect-launcher/-/sauce-connect-launcher-1.3.2.tgz#dfc675a258550809a8eaf457eb9162b943ddbaf0" - integrity sha1-38Z1olhVCAmo6vRX65FiuUPduvA= - dependencies: - adm-zip "~0.4.3" - async "^2.1.2" - https-proxy-agent "^5.0.0" - lodash "^4.16.6" - rimraf "^2.5.4" - -selenium-standalone@^7.1.0: - version "7.1.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/selenium-standalone/-/selenium-standalone-7.1.0.tgz#1192a4ad84f114137dd5deadcb81b0f56afe054a" - integrity sha1-EZKkrYTxFBN91d6ty4Gw9Wr+BUo= - dependencies: - commander "^7.2.0" - cross-spawn "^7.0.3" - debug "^4.3.1" - fs-extra "^10.0.0" - got "^11.8.2" - is-port-reachable "^3.0.0" - lodash.mapvalues "^4.6.0" - lodash.merge "^4.6.2" - minimist "^1.2.5" - mkdirp "^1.0.4" - progress "2.0.3" - tar-stream "2.2.0" - which "^2.0.2" - yauzl "^2.10.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha1-zNCvT4g1+9wmW4JGGq8MNmY/NOo= - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha1-rhbxZE2HPsrYQ7AwexQzYtTEIXI= - -source-map-support@^0.5.17: - version "0.5.21" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha1-BP58f54e0tZiIzwoyys1ufY/bk8= - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha1-dHIq8y6WFOnCh6jQu95IteLxomM= - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha1-QvEUWUpGzxqOMLCoT1bHjD7awh4= - dependencies: - safe-buffer "~5.2.0" - -tar-stream@2.2.0: - version "2.2.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha1-rK2EwoQTawYNw/qmRHSqmuvXcoc= - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -ts-node@^8.6.2: - version "8.10.2" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" - integrity sha1-7uA3ZGM7EjTd03+NuewQt17H+40= - dependencies: - arg "^4.1.0" - diff "^4.0.1" - make-error "^1.1.1" - source-map-support "^0.5.17" - yn "3.1.1" - -typescript@^3.7.5: - version "3.9.10" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" - integrity sha1-cPORCselHta+952ngAaQsZv3eLg= - -universalify@^2.0.0: - version "2.0.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha1-daSYTv7cSwiXXFrrc/Uw0C3yVxc= - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -which@^2.0.1, which@^2.0.2: - version "2.0.2" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha1-fGqN0KY2oDJ+ELWckobu6T8/UbE= - dependencies: - isexe "^2.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -yauzl@^2.10.0: - version "2.10.0" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - -yn@3.1.1: - version "3.1.1" - resolved "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha1-HodAGgnXZ8HV6rJqbkwYUYLS61A= diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/testassets/BasicTestApp/wwwroot/index.html dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/testassets/BasicTestApp/wwwroot/index.html --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/testassets/BasicTestApp/wwwroot/index.html 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/testassets/BasicTestApp/wwwroot/index.html 2023-11-13 13:20:34.000000000 +0000 @@ -56,7 +56,13 @@ + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs 2023-11-13 13:20:34.000000000 +0000 @@ -8,7 +8,7 @@ using Components.TestServer.RazorComponents; using Components.TestServer.RazorComponents.Pages.Forms; using Components.TestServer.Services; -using Microsoft.AspNetCore.Components.WebAssembly.Server; +using Microsoft.AspNetCore.Mvc; namespace TestServer; @@ -154,6 +154,25 @@ await response.WriteAsync("

    This is a non-Blazor endpoint

    That's all

    "); }); + endpoints.MapPost("api/antiforgery-form", ( + [FromForm] string value, + [FromForm(Name = "__RequestVerificationToken")] string? inFormCsrfToken, + [FromHeader(Name = "RequestVerificationToken")] string? inHeaderCsrfToken) => + { + // We shouldn't get this far without a valid CSRF token, but we'll double check it's there. + if (string.IsNullOrEmpty(inFormCsrfToken) && string.IsNullOrEmpty(inHeaderCsrfToken)) + { + throw new InvalidOperationException("Invalid POST to api/antiforgery-form!"); + } + + return TypedResults.Text($"

    Hello {value}!

    ", "text/html"); + }); + + endpoints.Map("/forms/endpoint-that-never-finishes-rendering", (HttpResponse response, CancellationToken token) => + { + return Task.Delay(Timeout.Infinite, token); + }); + static Task PerformRedirection(HttpRequest request, HttpResponse response) { response.Redirect(request.Query["external"] == "true" diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor 2023-11-13 13:20:34.000000000 +0000 @@ -18,12 +18,14 @@ "; - var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb => + var serviceProvider = GetServiceProvider(collection => collection.AddSingleton(new RenderFragment(rtb => { rtb.OpenElement(0, "script"); rtb.AddMarkupContent(1, "\n alert('Hello, "); @@ -1010,7 +993,7 @@ rtb.CloseElement(); rtb.AddMarkupContent(4, "\nAnd now with HTML encoding: "); rtb.AddContent(5, name); - })).BuildServiceProvider(); + }))); var htmlRenderer = GetHtmlRenderer(serviceProvider); await htmlRenderer.Dispatcher.InvokeAsync(async () => @@ -1030,12 +1013,12 @@ public async Task RenderComponentAsync_IgnoresNamedEvents() { // Arrange - var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb => + var serviceProvider = GetServiceProvider(collection => collection.AddSingleton(new RenderFragment(rtb => { rtb.OpenElement(0, "div"); rtb.AddNamedEvent("someevent", "somename"); rtb.CloseElement(); - })).BuildServiceProvider(); + }))); var htmlRenderer = GetHtmlRenderer(serviceProvider); await htmlRenderer.Dispatcher.InvokeAsync(async () => @@ -1053,12 +1036,12 @@ { // Arrange var formValueMapper = new TestFormValueMapper(); - var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb => + var serviceProvider = GetServiceProvider(collection => collection.AddSingleton(new RenderFragment(rtb => { rtb.OpenElement(0, "form"); rtb.AddNamedEvent("onsubmit", "somename"); rtb.CloseElement(); - })).BuildServiceProvider(); + }))); var htmlRenderer = GetHtmlRenderer(serviceProvider); await htmlRenderer.Dispatcher.InvokeAsync(async () => @@ -1076,14 +1059,14 @@ { // Arrange var formValueMapper = new TestFormValueMapper(); - var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb => + var serviceProvider = GetServiceProvider(collection => collection.AddSingleton(new RenderFragment(rtb => { rtb.OpenElement(0, "form"); rtb.AddNamedEvent("onsubmit", "some "); rtb.CloseElement(); })) .AddSingleton(new SupplyParameterFromFormValueProvider(formValueMapper, "")) - .AddSingleton(formValueMapper).BuildServiceProvider(); + .AddSingleton(formValueMapper)); var htmlRenderer = GetHtmlRenderer(serviceProvider); await htmlRenderer.Dispatcher.InvokeAsync(async () => @@ -1100,7 +1083,7 @@ public async Task RenderComponentAsync_AddsHiddenInputForNamedSubmitEvents_InsideNamedFormMappingScope() { // Arrange - var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb => + var serviceProvider = GetServiceProvider(collection => collection.AddSingleton(new RenderFragment(rtb => { rtb.OpenComponent(0); rtb.AddComponentParameter(1, nameof(FormMappingScope.Name), "myscope"); @@ -1111,7 +1094,7 @@ rtb.CloseElement(); })); rtb.CloseComponent(); - })).AddSingleton().BuildServiceProvider(); + })).AddSingleton()); var htmlRenderer = GetHtmlRenderer(serviceProvider); await htmlRenderer.Dispatcher.InvokeAsync(async () => @@ -1124,6 +1107,102 @@ }); } + [Theory] + [InlineData("https://example.com/", "https://example.com", "/")] + [InlineData("https://example.com/", "https://example.com/", "/")] + [InlineData("https://example.com/", "https://example.com/page", "/page")] + [InlineData("https://example.com/", "https://example.com/a/b/c", "/a/b/c")] + [InlineData("https://example.com/", "https://example.com/a/b/c?q=1&p=hello%20there", "/a/b/c?q=1&p=hello%20there")] + [InlineData("https://example.com/subdir/", "https://example.com/subdir", "/subdir")] + [InlineData("https://example.com/subdir/", "https://example.com/subdir/", "/subdir/")] + [InlineData("https://example.com/a/b/", "https://example.com/a/b/c?q=1&p=2", "/a/b/c?q=1&p=2")] + [InlineData("http://user:pass@xyz.example.com:1234/a/b/", "http://user:pass@xyz.example.com:1234/a/b/c&q=1&p=2", "/a/b/c&q=1&p=2")] + public async Task RenderComponentAsync_AddsActionAttributeWithCurrentUrlToFormWithoutAttributes_WhenNoActionSpecified( + string baseUrl, string currentUrl, string expectedAction) + { + // Arrange + var serviceProvider = GetServiceProvider(collection => collection.AddSingleton(new RenderFragment(rtb => + { + rtb.OpenElement(0, "form"); + rtb.CloseElement(); + })).AddScoped(_ => new TestNavigationManager(baseUrl, currentUrl))); + + var htmlRenderer = GetHtmlRenderer(serviceProvider); + await htmlRenderer.Dispatcher.InvokeAsync(async () => + { + // Act + var result = await htmlRenderer.RenderComponentAsync(); + + // Assert + Assert.Equal($"
    ", result.ToHtmlString()); + }); + } + + [Fact] + public async Task RenderComponentAsync_AddsActionAttributeWithCurrentUrlToFormWithAttributes_WhenNoActionSpecified() + { + // Arrange + var serviceProvider = GetServiceProvider(collection => collection.AddSingleton(new RenderFragment(rtb => + { + rtb.OpenElement(0, "form"); + rtb.AddAttribute(1, "method", "post"); + rtb.CloseElement(); + })).AddScoped()); + + var htmlRenderer = GetHtmlRenderer(serviceProvider); + await htmlRenderer.Dispatcher.InvokeAsync(async () => + { + // Act + var result = await htmlRenderer.RenderComponentAsync(); + + // Assert + Assert.Equal("
    ", result.ToHtmlString()); + }); + } + + [Fact] + public async Task RenderComponentAsync_DoesNotAddActionAttributeWithCurrentUrl_WhenActionIsExplicitlySpecified() + { + // Arrange + var serviceProvider = GetServiceProvider(collection => collection.AddSingleton(new RenderFragment(rtb => + { + rtb.OpenElement(0, "form"); + rtb.AddAttribute(1, "action", "https://example.com/explicit"); + rtb.CloseElement(); + })).AddScoped()); + + var htmlRenderer = GetHtmlRenderer(serviceProvider); + await htmlRenderer.Dispatcher.InvokeAsync(async () => + { + // Act + var result = await htmlRenderer.RenderComponentAsync(); + + // Assert + Assert.Equal("
    ", result.ToHtmlString()); + }); + } + + [Fact] + public async Task RenderComponentAsync_DoesNotAddActionAttributeWithCurrentUrl_WhenNoNavigationManagerIsRegistered() + { + // Arrange + var serviceProvider = GetServiceProvider(collection => collection.AddSingleton(new RenderFragment(rtb => + { + rtb.OpenElement(0, "form"); + rtb.CloseElement(); + }))); + + var htmlRenderer = GetHtmlRenderer(serviceProvider); + await htmlRenderer.Dispatcher.InvokeAsync(async () => + { + // Act + var result = await htmlRenderer.RenderComponentAsync(); + + // Assert + Assert.Equal("
    ", result.ToHtmlString()); + }); + } + // TODO: As above, but inside a FormMappingScope, showing its name also shows up void AssertHtmlContentEquals(IEnumerable expected, HtmlRootComponent actual) @@ -1243,7 +1322,8 @@ { status = "Loading..."; await Completion.Task; - await Task.Yield(); // So that the test has to await the quiescence task to observe the final outcome + await Task.Yield(); + // So that the test has to await the quiescence task to observe the final outcome status = "Finished loading"; } @@ -1294,6 +1374,7 @@ { var services = new ServiceCollection(); services.AddLogging(); + services.AddScoped(); serviceProvider = services.BuildServiceProvider(); } @@ -1309,4 +1390,30 @@ public void Map(FormValueMappingContext context) => throw new NotImplementedException(); } + + private class TestNavigationManager : NavigationManager + { + private string _baseUrl; + private string _currentUrl; + + public TestNavigationManager() + : this("https://www.example.com/", "https://www.example.com/page") + { + } + + public TestNavigationManager(string baseUrl, string currentUrl) + { + _baseUrl = baseUrl; + _currentUrl = currentUrl; + } + + protected override void EnsureInitialized() => Initialize(_baseUrl, _currentUrl); + } + + private IServiceProvider GetServiceProvider(Action configure = null) + { + var services = new ServiceCollection(); + configure?.Invoke(services); + return services.BuildServiceProvider(); + } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHost.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHost.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHost.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHost.cs 2023-11-13 13:20:34.000000000 +0000 @@ -1,6 +1,7 @@ // 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.CodeAnalysis; using System.Reflection.Metadata; using Microsoft.AspNetCore.Components.Infrastructure; using Microsoft.AspNetCore.Components.Web.Infrastructure; @@ -68,7 +69,7 @@ /// /// Disposes the host asynchronously. /// - /// A which respresents the completion of disposal. + /// A which represents the completion of disposal. public async ValueTask DisposeAsync() { if (_disposed) @@ -151,16 +152,16 @@ WebAssemblyNavigationManager.Instance.CreateLogger(loggerFactory); - RootComponentMapping[] mappings = []; + RootComponentOperationBatch? initialOperationBatch = null; if (Environment.GetEnvironmentVariable("__BLAZOR_WEBASSEMBLY_WAIT_FOR_ROOT_COMPONENTS") == "true") { // In Blazor web, we wait for the JS side to tell us about the components available // before we render the initial set of components. Any additional update goes through // UpdateRootComponents. // We do it this way to ensure that the persistent component state is only used the first time - // the wasm runtime is initalized and is done in the same way for both webassembly and blazor + // the wasm runtime is initialized and is done in the same way for both webassembly and blazor // web. - mappings = await InternalJSImportMethods.GetInitialComponentUpdate(); + initialOperationBatch = await InternalJSImportMethods.GetInitialComponentUpdate(); } var initializationTcs = new TaskCompletionSource(); @@ -172,7 +173,8 @@ // Here, we add each root component but don't await the returned tasks so that the // components can be processed in parallel. var count = rootComponents.Count; - var pendingRenders = new List(count + mappings.Length); + var initialOperationCount = initialOperationBatch?.Operations.Length ?? 0; + var pendingRenders = new List(count + initialOperationCount); for (var i = 0; i < count; i++) { var rootComponent = rootComponents[i]; @@ -182,16 +184,9 @@ rootComponent.Selector)); } - if (mappings != null) + if (initialOperationBatch is not null) { - for (var i = 0; i < mappings.Length; i++) - { - var rootComponent = mappings[i]; - pendingRenders.Add(renderer.AddComponentAsync( - rootComponent.ComponentType, - rootComponent.Parameters, - rootComponent.Selector)); - } + AddWebRootComponents(renderer, initialOperationBatch, pendingRenders); } // Now we wait for all components to finish rendering. @@ -211,4 +206,27 @@ await tcs.Task; } } + + [UnconditionalSuppressMessage("Trimming", "IL2072", Justification = "These are root components which belong to the user and are in assemblies that don't get trimmed.")] + private static void AddWebRootComponents(WebAssemblyRenderer renderer, RootComponentOperationBatch operationBatch, List pendingRenders) + { + var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager(); + var operations = operationBatch.Operations; + for (var i = 0; i < operations.Length; i++) + { + var operation = operations[i]; + if (operation.Type != RootComponentOperationType.Add) + { + throw new InvalidOperationException("All initial operations must be additions."); + } + + pendingRenders.Add(webRootComponentManager.AddRootComponentAsync( + operation.SsrComponentId, + operation.Descriptor!.ComponentType, + operation.Marker?.Key, + operation.Descriptor!.Parameters)); + } + + WebAssemblyRenderer.NotifyEndUpdateRootComponents(operationBatch.BatchId); + } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -9,6 +9,7 @@ true enable true + $(DefineConstants);COMPONENTS_WEBASSEMBLY true false @@ -37,9 +38,12 @@ + + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs 2023-11-13 13:20:34.000000000 +0000 @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices.JavaScript; using Microsoft.AspNetCore.Components.RenderTree; @@ -33,25 +32,41 @@ DefaultWebAssemblyJSRuntime.Instance.OnUpdateRootComponents += OnUpdateRootComponents; } - [UnconditionalSuppressMessage("Trimming", "IL2067", Justification = "These are root components which belong to the user and are in assemblies that don't get trimmed.")] - private void OnUpdateRootComponents(OperationDescriptor[] operations) + [UnconditionalSuppressMessage("Trimming", "IL2072", Justification = "These are root components which belong to the user and are in assemblies that don't get trimmed.")] + private void OnUpdateRootComponents(RootComponentOperationBatch batch) { - for (var i = 0; i < operations.Length; i++) + var webRootComponentManager = GetOrCreateWebRootComponentManager(); + for (var i = 0; i < batch.Operations.Length; i++) { - var (operation, componentType, parameters) = operations[i]; + var operation = batch.Operations[i]; switch (operation.Type) { case RootComponentOperationType.Add: - _ = AddComponentAsync(componentType!, parameters, operation.SelectorId!.Value.ToString(CultureInfo.InvariantCulture)); + _ = webRootComponentManager.AddRootComponentAsync( + operation.SsrComponentId, + operation.Descriptor!.ComponentType, + operation.Marker!.Value.Key!, + operation.Descriptor!.Parameters); break; case RootComponentOperationType.Update: - _ = RenderRootComponentAsync(operation.ComponentId!.Value, parameters); + _ = webRootComponentManager.UpdateRootComponentAsync( + operation.SsrComponentId, + operation.Descriptor!.ComponentType, + operation.Marker?.Key, + operation.Descriptor!.Parameters); break; case RootComponentOperationType.Remove: - RemoveRootComponent(operation.ComponentId!.Value); + webRootComponentManager.RemoveRootComponent(operation.SsrComponentId); break; } } + + NotifyEndUpdateRootComponents(batch.BatchId); + } + + public static void NotifyEndUpdateRootComponents(long batchId) + { + DefaultWebAssemblyJSRuntime.Instance.InvokeVoid("Blazor._internal.endUpdateRootComponents", batchId); } public override Dispatcher Dispatcher => NullDispatcher.Instance; diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs 2023-11-13 13:20:34.000000000 +0000 @@ -21,7 +21,7 @@ public ElementReferenceContext ElementReferenceContext { get; } - public event Action? OnUpdateRootComponents; + public event Action? OnUpdateRootComponents; [DynamicDependency(nameof(InvokeDotNet))] [DynamicDependency(nameof(EndInvokeJS))] @@ -105,31 +105,19 @@ } [DynamicDependency(JsonSerialized, typeof(RootComponentOperation))] + [DynamicDependency(JsonSerialized, typeof(RootComponentOperationBatch))] [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The correct members will be preserved by the above DynamicDependency")] - [SuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations.", Justification = "Types in that cache are components from the user assembly which are never trimmed.")] - internal static OperationDescriptor[] DeserializeOperations(string operationsJson) + internal static RootComponentOperationBatch DeserializeOperations(string operationsJson) { - var deserialized = JsonSerializer.Deserialize( + var deserialized = JsonSerializer.Deserialize( operationsJson, WebAssemblyComponentSerializationSettings.JsonSerializationOptions)!; - var operations = new OperationDescriptor[deserialized.Length]; - - for (var i = 0; i < deserialized.Length; i++) + for (var i = 0; i < deserialized.Operations.Length; i++) { - var operation = deserialized[i]; - if (operation.Type == RootComponentOperationType.Remove || - operation.Type == RootComponentOperationType.Update) - { - if (operation.ComponentId == null) - { - throw new InvalidOperationException($"The component operation of type '{operation.Type}' requires a '{nameof(operation.ComponentId)}' to be specified."); - } - } - + var operation = deserialized.Operations[i]; if (operation.Type == RootComponentOperationType.Remove) { - operations[i] = new(operation, null, ParameterView.Empty); continue; } @@ -138,32 +126,23 @@ throw new InvalidOperationException($"The component operation of type '{operation.Type}' requires a '{nameof(operation.Marker)}' to be specified."); } - Type? componentType = null; - if (operation.Type == RootComponentOperationType.Add) - { - if (operation.SelectorId == null) - { - throw new InvalidOperationException($"The component operation of type '{operation.Type}' requires a '{nameof(operation.SelectorId)}' to be specified."); - } - componentType = Instance._rootComponentCache.GetRootComponent(operation.Marker!.Value.Assembly!, operation.Marker.Value.TypeName!) + var componentType = Instance._rootComponentCache.GetRootComponent(operation.Marker!.Value.Assembly!, operation.Marker.Value.TypeName!) ?? throw new InvalidOperationException($"Root component type '{operation.Marker.Value.TypeName}' could not be found in the assembly '{operation.Marker.Value.Assembly}'."); - } - var parameters = DeserializeComponentParameters(operation.Marker.Value); - operations[i] = new(operation, componentType, parameters); + operation.Descriptor = new(componentType, parameters); } - return operations; + return deserialized; } - static ParameterView DeserializeComponentParameters(ComponentMarker marker) + static WebRootComponentParameters DeserializeComponentParameters(ComponentMarker marker) { var definitions = WebAssemblyComponentParameterDeserializer.GetParameterDefinitions(marker.ParameterDefinitions!); var values = WebAssemblyComponentParameterDeserializer.GetParameterValues(marker.ParameterValues!); var componentDeserializer = WebAssemblyComponentParameterDeserializer.Instance; var parameters = componentDeserializer.DeserializeParameters(definitions, values); - return parameters; + return new(parameters, definitions, values.AsReadOnly()); } [JSExport] @@ -183,29 +162,3 @@ return TransmitDataStreamToJS.TransmitStreamAsync(this, "Blazor._internal.receiveWebAssemblyDotNetDataStream", streamId, dotNetStreamReference); } } - -internal readonly struct OperationDescriptor -{ - public OperationDescriptor( - RootComponentOperation operation, - Type? componentType, - ParameterView parameters) - { - Operation = operation; - ComponentType = componentType; - Parameters = parameters; - } - - public RootComponentOperation Operation { get; } - - public Type? ComponentType { get; } - - public ParameterView Parameters { get; } - - public void Deconstruct(out RootComponentOperation operation, out Type? componentType, out ParameterView parameters) - { - operation = Operation; - componentType = ComponentType; - parameters = Parameters; - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/IInternalJSImportMethods.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/IInternalJSImportMethods.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/IInternalJSImportMethods.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/IInternalJSImportMethods.cs 2023-11-13 13:20:34.000000000 +0000 @@ -9,7 +9,7 @@ string GetApplicationEnvironment(); - void NavigationManager_EnableNavigationInterception(); + void NavigationManager_EnableNavigationInterception(int rendererId); void NavigationManager_ScrollToElement(string id); @@ -17,7 +17,7 @@ string NavigationManager_GetBaseUri(); - void NavigationManager_SetHasLocationChangingListeners(bool value); + void NavigationManager_SetHasLocationChangingListeners(int rendererId, bool value); int RegisteredComponents_GetRegisteredComponentsCount(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/InternalJSImportMethods.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/InternalJSImportMethods.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/InternalJSImportMethods.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/InternalJSImportMethods.cs 2023-11-13 13:20:34.000000000 +0000 @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.Runtime.InteropServices.JavaScript; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; @@ -19,33 +18,17 @@ => GetPersistedStateCore(); [UnconditionalSuppressMessage("Trimming", "IL2067", Justification = "These are root components which belong to the user and are in assemblies that don't get trimmed.")] - public static async Task GetInitialComponentUpdate() + public static async Task GetInitialComponentUpdate() { - var components = await InternalJSImportMethods.GetInitialUpdateCore(); - var operations = DefaultWebAssemblyJSRuntime.DeserializeOperations(components); - var mappings = new RootComponentMapping[operations.Length]; - - for (var i = 0; i < operations.Length; i++) - { - var (operation, component, parameters) = operations[i]; - if (operation.Type != RootComponentOperationType.Add) - { - throw new InvalidOperationException("All initial operations must be additions."); - } - mappings[i] = new RootComponentMapping( - component!, - operation.SelectorId!.Value.ToString(CultureInfo.InvariantCulture), - parameters); - } - - return mappings; + var components = await GetInitialUpdateCore(); + return DefaultWebAssemblyJSRuntime.DeserializeOperations(components); } public string GetApplicationEnvironment() => GetApplicationEnvironmentCore(); - public void NavigationManager_EnableNavigationInterception() - => NavigationManager_EnableNavigationInterceptionCore(); + public void NavigationManager_EnableNavigationInterception(int rendererId) + => NavigationManager_EnableNavigationInterceptionCore(rendererId); public void NavigationManager_ScrollToElement(string id) => NavigationManager_ScrollToElementCore(id); @@ -56,8 +39,8 @@ public string NavigationManager_GetBaseUri() => NavigationManager_GetBaseUriCore(); - public void NavigationManager_SetHasLocationChangingListeners(bool value) - => NavigationManager_SetHasLocationChangingListenersCore(value); + public void NavigationManager_SetHasLocationChangingListeners(int rendererId, bool value) + => NavigationManager_SetHasLocationChangingListenersCore(rendererId, value); public int RegisteredComponents_GetRegisteredComponentsCount() => RegisteredComponents_GetRegisteredComponentsCountCore(); @@ -84,7 +67,7 @@ private static partial string GetApplicationEnvironmentCore(); [JSImport(BrowserNavigationManagerInterop.EnableNavigationInterception, "blazor-internal")] - private static partial void NavigationManager_EnableNavigationInterceptionCore(); + private static partial void NavigationManager_EnableNavigationInterceptionCore(int rendererId); [JSImport(BrowserNavigationManagerInterop.ScrollToElement, "blazor-internal")] private static partial void NavigationManager_ScrollToElementCore(string id); @@ -96,7 +79,7 @@ private static partial string NavigationManager_GetBaseUriCore(); [JSImport(BrowserNavigationManagerInterop.SetHasLocationChangingListeners, "blazor-internal")] - private static partial void NavigationManager_SetHasLocationChangingListenersCore(bool value); + private static partial void NavigationManager_SetHasLocationChangingListenersCore(int rendererId, bool value); [JSImport(RegisteredComponentsInterop.GetRegisteredComponentsCount, "blazor-internal")] private static partial int RegisteredComponents_GetRegisteredComponentsCountCore(); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationInterception.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationInterception.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationInterception.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationInterception.cs 2023-11-13 13:20:34.000000000 +0000 @@ -11,7 +11,7 @@ public Task EnableNavigationInterceptionAsync() { - InternalJSImportMethods.Instance.NavigationManager_EnableNavigationInterception(); + InternalJSImportMethods.Instance.NavigationManager_EnableNavigationInterception((int)WebRendererId.WebAssembly); return Task.CompletedTask; } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationManager.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationManager.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationManager.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationManager.cs 2023-11-13 13:20:34.000000000 +0000 @@ -91,7 +91,7 @@ } protected override void SetNavigationLockState(bool value) - => InternalJSImportMethods.Instance.NavigationManager_SetHasLocationChangingListeners(value); + => InternalJSImportMethods.Instance.NavigationManager_SetHasLocationChangingListeners((int)WebRendererId.WebAssembly, value); private static partial class Log { diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/test/TestInternalJSImportMethods.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/test/TestInternalJSImportMethods.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/WebAssembly/WebAssembly/test/TestInternalJSImportMethods.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/WebAssembly/WebAssembly/test/TestInternalJSImportMethods.cs 2023-11-13 13:20:34.000000000 +0000 @@ -20,7 +20,7 @@ public string GetPersistedState() => null; - public void NavigationManager_EnableNavigationInterception() { } + public void NavigationManager_EnableNavigationInterception(int rendererId) { } public void NavigationManager_ScrollToElement(string id) { } @@ -30,7 +30,7 @@ public string NavigationManager_GetLocationHref() => "https://www.example.com/awesome-part-that-will-be-truncated-in-tests/cool"; - public void NavigationManager_SetHasLocationChangingListeners(bool value) { } + public void NavigationManager_SetHasLocationChangingListeners(int rendererId, bool value) { } public string RegisteredComponents_GetAssembly(int index) => string.Empty; diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/dist/Release/blazor.server.js dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/dist/Release/blazor.server.js --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/dist/Release/blazor.server.js 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/dist/Release/blazor.server.js 2023-11-13 13:20:34.000000000 +0000 @@ -1 +1 @@ -(()=>{var e={778:()=>{},77:()=>{},203:()=>{},200:()=>{},628:()=>{},321:()=>{}},t={};function n(o){var r=t[o];if(void 0!==r)return r.exports;var s=t[o]={exports:{}};return e[o](s,s.exports,n),s.exports}n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),(()=>{"use strict";var e,t,o;!function(e){const t=[],n="__jsObjectId",o="__dotNetObject",r="__byte[]",s="__dotNetStream",i="__jsStreamReferenceLength";let a,c;class l{constructor(e){this._jsObject=e,this._cachedFunctions=new Map}findFunction(e){const t=this._cachedFunctions.get(e);if(t)return t;let n,o=this._jsObject;if(e.split(".").forEach((t=>{if(!(t in o))throw new Error(`Could not find '${e}' ('${t}' was undefined).`);n=o,o=o[t]})),o instanceof Function)return o=o.bind(n),this._cachedFunctions.set(e,o),o;throw new Error(`The value '${e}' is not a function.`)}getWrappedObject(){return this._jsObject}}const h={0:new l(window)};h[0]._cachedFunctions.set("import",(e=>("string"==typeof e&&e.startsWith("./")&&(e=new URL(e.substr(2),document.baseURI).toString()),import(e))));let d,u=1;function p(e){t.push(e)}function f(e){if(e&&"object"==typeof e){h[u]=new l(e);const t={[n]:u};return u++,t}throw new Error(`Cannot create a JSObjectReference from the value '${e}'.`)}function g(e){let t=-1;if(e instanceof ArrayBuffer&&(e=new Uint8Array(e)),e instanceof Blob)t=e.size;else{if(!(e.buffer instanceof ArrayBuffer))throw new Error("Supplied value is not a typed array or blob.");if(void 0===e.byteLength)throw new Error(`Cannot create a JSStreamReference from the value '${e}' as it doesn't have a byteLength.`);t=e.byteLength}const o={[i]:t};try{const t=f(e);o[n]=t[n]}catch(t){throw new Error(`Cannot create a JSStreamReference from the value '${e}'.`)}return o}function m(e,n){c=e;const o=n?JSON.parse(n,((e,n)=>t.reduce(((t,n)=>n(e,t)),n))):null;return c=void 0,o}function v(){if(void 0===a)throw new Error("No call dispatcher has been set.");if(null===a)throw new Error("There are multiple .NET runtimes present, so a default dispatcher could not be resolved. Use DotNetObject to invoke .NET instance methods.");return a}e.attachDispatcher=function(e){const t=new y(e);return void 0===a?a=t:a&&(a=null),t},e.attachReviver=p,e.invokeMethod=function(e,t,...n){return v().invokeDotNetStaticMethod(e,t,...n)},e.invokeMethodAsync=function(e,t,...n){return v().invokeDotNetStaticMethodAsync(e,t,...n)},e.createJSObjectReference=f,e.createJSStreamReference=g,e.disposeJSObjectReference=function(e){const t=e&&e[n];"number"==typeof t&&b(t)},function(e){e[e.Default=0]="Default",e[e.JSObjectReference=1]="JSObjectReference",e[e.JSStreamReference=2]="JSStreamReference",e[e.JSVoidResult=3]="JSVoidResult"}(d=e.JSCallResultType||(e.JSCallResultType={}));class y{constructor(e){this._dotNetCallDispatcher=e,this._byteArraysToBeRevived=new Map,this._pendingDotNetToJSStreams=new Map,this._pendingAsyncCalls={},this._nextAsyncCallId=1}getDotNetCallDispatcher(){return this._dotNetCallDispatcher}invokeJSFromDotNet(e,t,n,o){const r=m(this,t),s=I(_(e,o)(...r||[]),n);return null==s?null:T(this,s)}beginInvokeJSFromDotNet(e,t,n,o,r){const s=new Promise((e=>{const o=m(this,n);e(_(t,r)(...o||[]))}));e&&s.then((t=>T(this,[e,!0,I(t,o)]))).then((t=>this._dotNetCallDispatcher.endInvokeJSFromDotNet(e,!0,t)),(t=>this._dotNetCallDispatcher.endInvokeJSFromDotNet(e,!1,JSON.stringify([e,!1,w(t)]))))}endInvokeDotNetFromJS(e,t,n){const o=t?m(this,n):new Error(n);this.completePendingCall(parseInt(e,10),t,o)}invokeDotNetStaticMethod(e,t,...n){return this.invokeDotNetMethod(e,t,null,n)}invokeDotNetStaticMethodAsync(e,t,...n){return this.invokeDotNetMethodAsync(e,t,null,n)}invokeDotNetMethod(e,t,n,o){if(this._dotNetCallDispatcher.invokeDotNetFromJS){const r=T(this,o),s=this._dotNetCallDispatcher.invokeDotNetFromJS(e,t,n,r);return s?m(this,s):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeDotNetMethodAsync instead.")}invokeDotNetMethodAsync(e,t,n,o){if(e&&n)throw new Error(`For instance method calls, assemblyName should be null. Received '${e}'.`);const r=this._nextAsyncCallId++,s=new Promise(((e,t)=>{this._pendingAsyncCalls[r]={resolve:e,reject:t}}));try{const s=T(this,o);this._dotNetCallDispatcher.beginInvokeDotNetFromJS(r,e,t,n,s)}catch(e){this.completePendingCall(r,!1,e)}return s}receiveByteArray(e,t){this._byteArraysToBeRevived.set(e,t)}processByteArray(e){const t=this._byteArraysToBeRevived.get(e);return t?(this._byteArraysToBeRevived.delete(e),t):null}supplyDotNetStream(e,t){if(this._pendingDotNetToJSStreams.has(e)){const n=this._pendingDotNetToJSStreams.get(e);this._pendingDotNetToJSStreams.delete(e),n.resolve(t)}else{const n=new C;n.resolve(t),this._pendingDotNetToJSStreams.set(e,n)}}getDotNetStreamPromise(e){let t;if(this._pendingDotNetToJSStreams.has(e))t=this._pendingDotNetToJSStreams.get(e).streamPromise,this._pendingDotNetToJSStreams.delete(e);else{const n=new C;this._pendingDotNetToJSStreams.set(e,n),t=n.streamPromise}return t}completePendingCall(e,t,n){if(!this._pendingAsyncCalls.hasOwnProperty(e))throw new Error(`There is no pending async call with ID ${e}.`);const o=this._pendingAsyncCalls[e];delete this._pendingAsyncCalls[e],t?o.resolve(n):o.reject(n)}}function w(e){return e instanceof Error?`${e.message}\n${e.stack}`:e?e.toString():"null"}function _(e,t){const n=h[t];if(n)return n.findFunction(e);throw new Error(`JS object instance with ID ${t} does not exist (has it been disposed?).`)}function b(e){delete h[e]}e.findJSFunction=_,e.disposeJSObjectReferenceById=b;class S{constructor(e,t){this._id=e,this._callDispatcher=t}invokeMethod(e,...t){return this._callDispatcher.invokeDotNetMethod(null,e,this._id,t)}invokeMethodAsync(e,...t){return this._callDispatcher.invokeDotNetMethodAsync(null,e,this._id,t)}dispose(){this._callDispatcher.invokeDotNetMethodAsync(null,"__Dispose",this._id,null).catch((e=>console.error(e)))}serializeAsArg(){return{[o]:this._id}}}e.DotNetObject=S,p((function(e,t){if(t&&"object"==typeof t){if(t.hasOwnProperty(o))return new S(t[o],c);if(t.hasOwnProperty(n)){const e=t[n],o=h[e];if(o)return o.getWrappedObject();throw new Error(`JS object instance with Id '${e}' does not exist. It may have been disposed.`)}if(t.hasOwnProperty(r)){const e=t[r],n=c.processByteArray(e);if(void 0===n)throw new Error(`Byte array index '${e}' does not exist.`);return n}if(t.hasOwnProperty(s)){const e=t[s],n=c.getDotNetStreamPromise(e);return new E(n)}}return t}));class E{constructor(e){this._streamPromise=e}stream(){return this._streamPromise}async arrayBuffer(){return new Response(await this.stream()).arrayBuffer()}}class C{constructor(){this.streamPromise=new Promise(((e,t)=>{this.resolve=e,this.reject=t}))}}function I(e,t){switch(t){case d.Default:return e;case d.JSObjectReference:return f(e);case d.JSStreamReference:return g(e);case d.JSVoidResult:return null;default:throw new Error(`Invalid JS call result type '${t}'.`)}}let k=0;function T(e,t){k=0,c=e;const n=JSON.stringify(t,D);return c=void 0,n}function D(e,t){if(t instanceof S)return t.serializeAsArg();if(t instanceof Uint8Array){c.getDotNetCallDispatcher().sendByteArray(k,t);const e={[r]:k};return k++,e}return t}}(e||(e={})),function(e){e[e.prependFrame=1]="prependFrame",e[e.removeFrame=2]="removeFrame",e[e.setAttribute=3]="setAttribute",e[e.removeAttribute=4]="removeAttribute",e[e.updateText=5]="updateText",e[e.stepIn=6]="stepIn",e[e.stepOut=7]="stepOut",e[e.updateMarkup=8]="updateMarkup",e[e.permutationListEntry=9]="permutationListEntry",e[e.permutationListEnd=10]="permutationListEnd"}(t||(t={})),function(e){e[e.element=1]="element",e[e.text=2]="text",e[e.attribute=3]="attribute",e[e.component=4]="component",e[e.region=5]="region",e[e.elementReferenceCapture=6]="elementReferenceCapture",e[e.markup=8]="markup",e[e.namedEvent=10]="namedEvent"}(o||(o={}));class r{constructor(e,t){this.componentId=e,this.fieldValue=t}static fromEvent(e,t){const n=t.target;if(n instanceof Element){const t=function(e){return e instanceof HTMLInputElement?e.type&&"checkbox"===e.type.toLowerCase()?{value:e.checked}:{value:e.value}:e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement?{value:e.value}:null}(n);if(t)return new r(e,t.value)}return null}}const s=new Map,i=new Map,a=[];function c(e){return s.get(e)}function l(e){const t=s.get(e);return(null==t?void 0:t.browserEventName)||e}function h(e,t){e.forEach((e=>s.set(e,t)))}function d(e){const t=[];for(let n=0;ne.selected)).map((e=>e.value))}}{const e=function(e){return!!e&&"INPUT"===e.tagName&&"checkbox"===e.getAttribute("type")}(t);return{value:e?!!t.checked:t.value}}}}),h(["copy","cut","paste"],{createEventArgs:e=>({type:e.type})}),h(["drag","dragend","dragenter","dragleave","dragover","dragstart","drop"],{createEventArgs:e=>{return{...u(t=e),dataTransfer:t.dataTransfer?{dropEffect:t.dataTransfer.dropEffect,effectAllowed:t.dataTransfer.effectAllowed,files:Array.from(t.dataTransfer.files).map((e=>e.name)),items:Array.from(t.dataTransfer.items).map((e=>({kind:e.kind,type:e.type}))),types:t.dataTransfer.types}:null};var t}}),h(["focus","blur","focusin","focusout"],{createEventArgs:e=>({type:e.type})}),h(["keydown","keyup","keypress"],{createEventArgs:e=>{return{key:(t=e).key,code:t.code,location:t.location,repeat:t.repeat,ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),h(["contextmenu","click","mouseover","mouseout","mousemove","mousedown","mouseup","mouseleave","mouseenter","dblclick"],{createEventArgs:e=>u(e)}),h(["error"],{createEventArgs:e=>{return{message:(t=e).message,filename:t.filename,lineno:t.lineno,colno:t.colno,type:t.type};var t}}),h(["loadstart","timeout","abort","load","loadend","progress"],{createEventArgs:e=>{return{lengthComputable:(t=e).lengthComputable,loaded:t.loaded,total:t.total,type:t.type};var t}}),h(["touchcancel","touchend","touchmove","touchenter","touchleave","touchstart"],{createEventArgs:e=>{return{detail:(t=e).detail,touches:d(t.touches),targetTouches:d(t.targetTouches),changedTouches:d(t.changedTouches),ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),h(["gotpointercapture","lostpointercapture","pointercancel","pointerdown","pointerenter","pointerleave","pointermove","pointerout","pointerover","pointerup"],{createEventArgs:e=>{return{...u(t=e),pointerId:t.pointerId,width:t.width,height:t.height,pressure:t.pressure,tiltX:t.tiltX,tiltY:t.tiltY,pointerType:t.pointerType,isPrimary:t.isPrimary};var t}}),h(["wheel","mousewheel"],{createEventArgs:e=>{return{...u(t=e),deltaX:t.deltaX,deltaY:t.deltaY,deltaZ:t.deltaZ,deltaMode:t.deltaMode};var t}}),h(["cancel","close","toggle"],{createEventArgs:()=>({})});const p=["date","datetime-local","month","time","week"],f=new Map;let g,m,v=0;const y={async add(e,t,n){if(!n)throw new Error("initialParameters must be an object, even if empty.");const o="__bl-dynamic-root:"+(++v).toString();f.set(o,e);const r=await b().invokeMethodAsync("AddRootComponent",t,o),s=new _(r,m[t]);return await s.setParameters(n),s}};class w{invoke(e){return this._callback(e)}setCallback(t){this._selfJSObjectReference||(this._selfJSObjectReference=e.createJSObjectReference(this)),this._callback=t}getJSObjectReference(){return this._selfJSObjectReference}dispose(){this._selfJSObjectReference&&e.disposeJSObjectReference(this._selfJSObjectReference)}}class _{constructor(e,t){this._jsEventCallbackWrappers=new Map,this._componentId=e;for(const e of t)"eventcallback"===e.type&&this._jsEventCallbackWrappers.set(e.name.toLowerCase(),new w)}setParameters(e){const t={},n=Object.entries(e||{}),o=n.length;for(const[e,o]of n){const n=this._jsEventCallbackWrappers.get(e.toLowerCase());n&&o?(n.setCallback(o),t[e]=n.getJSObjectReference()):t[e]=o}return b().invokeMethodAsync("SetRootComponentParameters",this._componentId,o,t)}async dispose(){if(null!==this._componentId){await b().invokeMethodAsync("RemoveRootComponent",this._componentId),this._componentId=null;for(const e of this._jsEventCallbackWrappers.values())e.dispose()}}}function b(){if(!g)throw new Error("Dynamic root components have not been enabled in this application.");return g}const S=new Map,E=[];let C;const I=new Promise((e=>{C=e}));function k(e,t,n){return D(e,t.eventHandlerId,(()=>T(e).invokeMethodAsync("DispatchEventAsync",t,n)))}function T(e){const t=S.get(e);if(!t)throw new Error(`No interop methods are registered for renderer ${e}`);return t}let D=(e,t,n)=>n();const x=M(["abort","blur","cancel","canplay","canplaythrough","change","close","cuechange","durationchange","emptied","ended","error","focus","load","loadeddata","loadedmetadata","loadend","loadstart","mouseenter","mouseleave","pointerenter","pointerleave","pause","play","playing","progress","ratechange","reset","scroll","seeked","seeking","stalled","submit","suspend","timeupdate","toggle","unload","volumechange","waiting","DOMNodeInsertedIntoDocument","DOMNodeRemovedFromDocument"]),R={submit:!0},P=M(["click","dblclick","mousedown","mousemove","mouseup"]);class A{constructor(e){this.browserRendererId=e,this.afterClickCallbacks=[];const t=++A.nextEventDelegatorId;this.eventsCollectionKey=`_blazorEvents_${t}`,this.eventInfoStore=new N(this.onGlobalEvent.bind(this))}setListener(e,t,n,o){const r=this.getEventHandlerInfosForElement(e,!0),s=r.getHandler(t);if(s)this.eventInfoStore.update(s.eventHandlerId,n);else{const s={element:e,eventName:t,eventHandlerId:n,renderingComponentId:o};this.eventInfoStore.add(s),r.setHandler(t,s)}}getHandler(e){return this.eventInfoStore.get(e)}removeListener(e){const t=this.eventInfoStore.remove(e);if(t){const e=t.element,n=this.getEventHandlerInfosForElement(e,!1);n&&n.removeHandler(t.eventName)}}notifyAfterClick(e){this.afterClickCallbacks.push(e),this.eventInfoStore.addGlobalListener("click")}setStopPropagation(e,t,n){this.getEventHandlerInfosForElement(e,!0).stopPropagation(t,n)}setPreventDefault(e,t,n){this.getEventHandlerInfosForElement(e,!0).preventDefault(t,n)}onGlobalEvent(e){if(!(e.target instanceof Element))return;this.dispatchGlobalEventToAllElements(e.type,e);const t=(n=e.type,i.get(n));var n;t&&t.forEach((t=>this.dispatchGlobalEventToAllElements(t,e))),"click"===e.type&&this.afterClickCallbacks.forEach((t=>t(e)))}dispatchGlobalEventToAllElements(e,t){const n=t.composedPath();let o=n.shift(),s=null,i=!1;const a=Object.prototype.hasOwnProperty.call(x,e);let l=!1;for(;o;){const u=o,p=this.getEventHandlerInfosForElement(u,!1);if(p){const n=p.getHandler(e);if(n&&(h=u,d=t.type,!((h instanceof HTMLButtonElement||h instanceof HTMLInputElement||h instanceof HTMLTextAreaElement||h instanceof HTMLSelectElement)&&Object.prototype.hasOwnProperty.call(P,d)&&h.disabled))){if(!i){const n=c(e);s=(null==n?void 0:n.createEventArgs)?n.createEventArgs(t):{},i=!0}Object.prototype.hasOwnProperty.call(R,t.type)&&t.preventDefault(),k(this.browserRendererId,{eventHandlerId:n.eventHandlerId,eventName:e,eventFieldInfo:r.fromEvent(n.renderingComponentId,t)},s)}p.stopPropagation(e)&&(l=!0),p.preventDefault(e)&&t.preventDefault()}o=a||l?void 0:n.shift()}var h,d}getEventHandlerInfosForElement(e,t){return Object.prototype.hasOwnProperty.call(e,this.eventsCollectionKey)?e[this.eventsCollectionKey]:t?e[this.eventsCollectionKey]=new U:null}}A.nextEventDelegatorId=0;class N{constructor(e){this.globalListener=e,this.infosByEventHandlerId={},this.countByEventName={},a.push(this.handleEventNameAliasAdded.bind(this))}add(e){if(this.infosByEventHandlerId[e.eventHandlerId])throw new Error(`Event ${e.eventHandlerId} is already tracked`);this.infosByEventHandlerId[e.eventHandlerId]=e,this.addGlobalListener(e.eventName)}get(e){return this.infosByEventHandlerId[e]}addGlobalListener(e){if(e=l(e),Object.prototype.hasOwnProperty.call(this.countByEventName,e))this.countByEventName[e]++;else{this.countByEventName[e]=1;const t=Object.prototype.hasOwnProperty.call(x,e);document.addEventListener(e,this.globalListener,t)}}update(e,t){if(Object.prototype.hasOwnProperty.call(this.infosByEventHandlerId,t))throw new Error(`Event ${t} is already tracked`);const n=this.infosByEventHandlerId[e];delete this.infosByEventHandlerId[e],n.eventHandlerId=t,this.infosByEventHandlerId[t]=n}remove(e){const t=this.infosByEventHandlerId[e];if(t){delete this.infosByEventHandlerId[e];const n=l(t.eventName);0==--this.countByEventName[n]&&(delete this.countByEventName[n],document.removeEventListener(n,this.globalListener))}return t}handleEventNameAliasAdded(e,t){if(Object.prototype.hasOwnProperty.call(this.countByEventName,e)){const n=this.countByEventName[e];delete this.countByEventName[e],document.removeEventListener(e,this.globalListener),this.addGlobalListener(t),this.countByEventName[t]+=n-1}}}class U{constructor(){this.handlers={},this.preventDefaultFlags=null,this.stopPropagationFlags=null}getHandler(e){return Object.prototype.hasOwnProperty.call(this.handlers,e)?this.handlers[e]:null}setHandler(e,t){this.handlers[e]=t}removeHandler(e){delete this.handlers[e]}preventDefault(e,t){return void 0!==t&&(this.preventDefaultFlags=this.preventDefaultFlags||{},this.preventDefaultFlags[e]=t),!!this.preventDefaultFlags&&this.preventDefaultFlags[e]}stopPropagation(e,t){return void 0!==t&&(this.stopPropagationFlags=this.stopPropagationFlags||{},this.stopPropagationFlags[e]=t),!!this.stopPropagationFlags&&this.stopPropagationFlags[e]}}function M(e){const t={};return e.forEach((e=>{t[e]=!0})),t}const B=Symbol(),L=Symbol(),$=Symbol();function O(e,t){if(B in e)return e;const n=[];if(e.childNodes.length>0){if(!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");e.childNodes.forEach((t=>{const o=O(t,!0);o[L]=e,n.push(o)}))}return e[B]=n,e}function F(e){const t=K(e);for(;t.length;)W(e,0)}function H(e,t){const n=document.createComment("!");return j(n,e,t),n}function j(e,t,n){const o=e;let r=e;if(B in e){const t=Q(o);if(t!==e){const n=new Range;n.setStartBefore(e),n.setEndAfter(t),r=n.extractContents()}}const s=z(o);if(s){const e=K(s),t=Array.prototype.indexOf.call(e,o);e.splice(t,1),delete o[L]}const i=K(t);if(n0;)W(n,0)}const o=n;o.parentNode.removeChild(o)}function z(e){return e[L]||null}function q(e,t){return K(e)[t]}function J(e){const t=Y(e);return"http://www.w3.org/2000/svg"===t.namespaceURI&&"foreignObject"!==t.tagName}function K(e){return e[B]}function V(e){const t=K(z(e));return t[Array.prototype.indexOf.call(t,e)+1]||null}function X(e,t){const n=K(e);t.forEach((e=>{e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=Q(e.moveRangeStart)})),t.forEach((t=>{const o=document.createComment("marker");t.moveToBeforeMarker=o;const r=n[t.toSiblingIndex+1];r?r.parentNode.insertBefore(o,r):G(o,e)})),t.forEach((e=>{const t=e.moveToBeforeMarker,n=t.parentNode,o=e.moveRangeStart,r=e.moveRangeEnd;let s=o;for(;s;){const e=s.nextSibling;if(n.insertBefore(s,t),s===r)break;s=e}n.removeChild(t)})),t.forEach((e=>{n[e.toSiblingIndex]=e.moveRangeStart}))}function Y(e){if(e instanceof Element||e instanceof DocumentFragment)return e;if(e instanceof Comment)return e.parentNode;throw new Error("Not a valid logical element")}function G(e,t){if(t instanceof Element||t instanceof DocumentFragment)t.appendChild(e);else{if(!(t instanceof Comment))throw new Error(`Cannot append node because the parent is not a valid logical element. Parent: ${t}`);{const n=V(t);n?n.parentNode.insertBefore(e,n):G(e,z(t))}}}function Q(e){if(e instanceof Element||e instanceof DocumentFragment)return e;const t=V(e);if(t)return t.previousSibling;{const t=z(e);return t instanceof Element||t instanceof DocumentFragment?t.lastChild:Q(t)}}function Z(e){return`_bl_${e}`}const ee="__internalId";e.attachReviver(((e,t)=>t&&"object"==typeof t&&Object.prototype.hasOwnProperty.call(t,ee)&&"string"==typeof t[ee]?function(e){const t=`[${Z(e)}]`;return document.querySelector(t)}(t[ee]):t));const te="_blazorDeferredValue";function ne(e){return"select-multiple"===e.type}function oe(e,t){e.value=t||""}function re(e,t){e instanceof HTMLSelectElement?ne(e)?function(e,t){t||(t=[]);for(let n=0;n{Ce()&&function(e,t){if(0!==e.button||function(e){return e.ctrlKey||e.shiftKey||e.altKey||e.metaKey}(e))return;if(e.defaultPrevented)return;const n=function(e){const t=!window._blazorDisableComposedPath&&e.composedPath&&e.composedPath();if(t){for(let e=0;edocument.baseURI,getLocationHref:()=>location.href,scrollToElement:Ue};function Ue(e){const t=document.getElementById(e);return!!t&&(t.scrollIntoView(),!0)}function Me(e,t,n=!1){const o=Se(e);!t.forceLoad&&we(o)?We()?Be(o,!1,t.replaceHistoryEntry,t.historyEntryState,n):be(o,t.replaceHistoryEntry):function(e,t){if(location.href===e){const t=e+"?";history.replaceState(null,"",t),location.replace(e)}else t?location.replace(e):location.href=e}(e,t.replaceHistoryEntry)}async function Be(e,t,n,o=void 0,r=!1){if(Oe(),function(e){const t=e.indexOf("#");return t>-1&&location.href.replace(location.hash,"")===e.substring(0,t)}(e))!function(e,t,n){Le(e,t,n);const o=e.indexOf("#");o!==e.length-1&&Ue(e.substring(o+1))}(e,n,o);else{if(!r&&ke&&!await Fe(e,o,t))return;ve=!0,Le(e,n,o),await He(t)}}function Le(e,t,n=void 0){t?history.replaceState({userState:n,_index:Te},"",e):(Te++,history.pushState({userState:n,_index:Te},"",e))}function $e(e){return new Promise((t=>{const n=Pe;Pe=()=>{Pe=n,t()},history.go(e)}))}function Oe(){Ae&&(Ae(!1),Ae=null)}function Fe(e,t,n){return new Promise((o=>{Oe(),Re?(De++,Ae=o,Re(De,e,t,n)):o(!1)}))}async function He(e){var t;xe&&await xe(location.href,null===(t=history.state)||void 0===t?void 0:t.userState,e)}async function je(e){var t,n;Pe&&We()&&await Pe(e),Te=null!==(n=null===(t=history.state)||void 0===t?void 0:t._index)&&void 0!==n?n:0}function We(){return Ce()||!_e()}const ze={focus:function(e,t){if(e instanceof HTMLElement)e.focus({preventScroll:t});else{if(!(e instanceof SVGElement))throw new Error("Unable to focus an invalid element.");if(!e.hasAttribute("tabindex"))throw new Error("Unable to focus an SVG element that does not have a tabindex.");e.focus({preventScroll:t})}},focusBySelector:function(e,t){const n=document.querySelector(e);n&&(n.hasAttribute("tabindex")||(n.tabIndex=-1),n.focus({preventScroll:!0}))}},qe={init:function(e,t,n,o=50){const r=Ke(t);(r||document.documentElement).style.overflowAnchor="none";const s=document.createRange();u(n.parentElement)&&(t.style.display="table-row",n.style.display="table-row");const i=new IntersectionObserver((function(o){o.forEach((o=>{var r;if(!o.isIntersecting)return;s.setStartAfter(t),s.setEndBefore(n);const i=s.getBoundingClientRect().height,a=null===(r=o.rootBounds)||void 0===r?void 0:r.height;o.target===t?e.invokeMethodAsync("OnSpacerBeforeVisible",o.intersectionRect.top-o.boundingClientRect.top,i,a):o.target===n&&n.offsetHeight>0&&e.invokeMethodAsync("OnSpacerAfterVisible",o.boundingClientRect.bottom-o.intersectionRect.bottom,i,a)}))}),{root:r,rootMargin:`${o}px`});i.observe(t),i.observe(n);const a=d(t),c=d(n),{observersByDotNetObjectId:l,id:h}=Ve(e);function d(e){const t={attributes:!0},n=new MutationObserver(((n,o)=>{u(e.parentElement)&&(o.disconnect(),e.style.display="table-row",o.observe(e,t)),i.unobserve(e),i.observe(e)}));return n.observe(e,t),n}function u(e){return null!==e&&(e instanceof HTMLTableElement&&""===e.style.display||"table"===e.style.display||e instanceof HTMLTableSectionElement&&""===e.style.display||"table-row-group"===e.style.display)}l[h]={intersectionObserver:i,mutationObserverBefore:a,mutationObserverAfter:c}},dispose:function(e){const{observersByDotNetObjectId:t,id:n}=Ve(e),o=t[n];o&&(o.intersectionObserver.disconnect(),o.mutationObserverBefore.disconnect(),o.mutationObserverAfter.disconnect(),e.dispose(),delete t[n])}},Je=Symbol();function Ke(e){return e&&e!==document.body&&e!==document.documentElement?"visible"!==getComputedStyle(e).overflowY?e:Ke(e.parentElement):null}function Ve(e){var t;const n=e._callDispatcher,o=e._id;return null!==(t=n[Je])&&void 0!==t||(n[Je]={}),{observersByDotNetObjectId:n[Je],id:o}}const Xe={getAndRemoveExistingTitle:function(){var e;const t=document.head?document.head.getElementsByTagName("title"):[];if(0===t.length)return null;let n=null;for(let o=t.length-1;o>=0;o--){const r=t[o],s=r.previousSibling;s instanceof Comment&&null!==z(s)||(null===n&&(n=r.textContent),null===(e=r.parentNode)||void 0===e||e.removeChild(r))}return n}},Ye={init:function(e,t){t._blazorInputFileNextFileId=0,t.addEventListener("click",(function(){t.value=""})),t.addEventListener("change",(function(){t._blazorFilesById={};const n=Array.prototype.map.call(t.files,(function(e){const n={id:++t._blazorInputFileNextFileId,lastModified:new Date(e.lastModified).toISOString(),name:e.name,size:e.size,contentType:e.type,readPromise:void 0,arrayBuffer:void 0,blob:e};return t._blazorFilesById[n.id]=n,n}));e.invokeMethodAsync("NotifyChange",n)}))},toImageFile:async function(e,t,n,o,r){const s=Ge(e,t),i=await new Promise((function(e){const t=new Image;t.onload=function(){URL.revokeObjectURL(t.src),e(t)},t.onerror=function(){t.onerror=null,URL.revokeObjectURL(t.src)},t.src=URL.createObjectURL(s.blob)})),a=await new Promise((function(e){var t;const s=Math.min(1,o/i.width),a=Math.min(1,r/i.height),c=Math.min(s,a),l=document.createElement("canvas");l.width=Math.round(i.width*c),l.height=Math.round(i.height*c),null===(t=l.getContext("2d"))||void 0===t||t.drawImage(i,0,0,l.width,l.height),l.toBlob(e,n)})),c={id:++e._blazorInputFileNextFileId,lastModified:s.lastModified,name:s.name,size:(null==a?void 0:a.size)||0,contentType:n,blob:a||s.blob};return e._blazorFilesById[c.id]=c,c},readFileData:async function(e,t){return Ge(e,t).blob}};function Ge(e,t){const n=e._blazorFilesById[t];if(!n)throw new Error(`There is no file with ID ${t}. The file list may have changed. See https://aka.ms/aspnet/blazor-input-file-multiple-selections.`);return n}const Qe=new Set,Ze={enableNavigationPrompt:function(e){0===Qe.size&&window.addEventListener("beforeunload",et),Qe.add(e)},disableNavigationPrompt:function(e){Qe.delete(e),0===Qe.size&&window.removeEventListener("beforeunload",et)}};function et(e){e.preventDefault(),e.returnValue=!0}async function tt(e,t,n){return e instanceof Blob?await async function(e,t,n){const o=e.slice(t,t+n),r=await o.arrayBuffer();return new Uint8Array(r)}(e,t,n):function(e,t,n){return new Uint8Array(e.buffer,e.byteOffset+t,n)}(e,t,n)}new Map;const nt={navigateTo:function(e,t,n=!1){Me(e,t instanceof Object?t:{forceLoad:t,replaceHistoryEntry:n})},registerCustomEventType:function(e,t){if(!t)throw new Error("The options parameter is required.");if(s.has(e))throw new Error(`The event '${e}' is already registered.`);if(t.browserEventName){const n=i.get(t.browserEventName);n?n.push(e):i.set(t.browserEventName,[e]),a.forEach((n=>n(e,t.browserEventName)))}s.set(e,t)},rootComponents:y,runtime:{},_internal:{navigationManager:Ne,domWrapper:ze,Virtualize:qe,PageTitle:Xe,InputFile:Ye,NavigationLock:Ze,getJSDataStreamChunk:tt,attachWebRendererInterop:function(t,n,o,r){if(S.has(t))throw new Error(`Interop methods are already registered for renderer ${t}`);S.set(t,n),Object.keys(o).length>0&&function(t,n,o){if(g)throw new Error("Dynamic root components have already been enabled.");g=t,m=n;for(const[t,r]of Object.entries(o)){const o=e.findJSFunction(t,0);for(const e of r)o(e,n[e])}}(T(t),o,r),C(),function(e){for(const t of E)t(e)}(t)}}};var ot;window.Blazor=nt,function(e){e[e.Trace=0]="Trace",e[e.Debug=1]="Debug",e[e.Information=2]="Information",e[e.Warning=3]="Warning",e[e.Error=4]="Error",e[e.Critical=5]="Critical",e[e.None=6]="None"}(ot||(ot={}));class rt{log(e,t){}}rt.instance=new rt;class st{constructor(e){this.minLevel=e}log(e,t){if(e>=this.minLevel){const n=`[${(new Date).toISOString()}] ${ot[e]}: ${t}`;switch(e){case ot.Critical:case ot.Error:console.error(n);break;case ot.Warning:console.warn(n);break;case ot.Information:console.info(n);break;default:console.log(n)}}}}const it=/^\s*Blazor-Server-Component-State:(?[a-zA-Z0-9+/=]+)$/;function at(e,t){var n;if(e.nodeType===Node.COMMENT_NODE){const o=e.textContent||"",r=t.exec(o),s=r&&r.groups&&r.groups.state;return s&&(null===(n=e.parentNode)||void 0===n||n.removeChild(e)),s}if(!e.hasChildNodes())return;const o=e.childNodes;for(let e=0;e.*)$/);function ht(e,t){const n=e.currentElement;var o,r,s;if(n&&n.nodeType===Node.COMMENT_NODE&&n.textContent){const i=lt.exec(n.textContent),a=i&&i.groups&&i.groups.descriptor;if(!a)return;!function(e){if(e.parentNode instanceof Document)throw new Error("Root components cannot be marked as interactive. The element must be rendered statically so that scripts are not evaluated multiple times.")}(n);try{const i=function(e){const t=JSON.parse(e),{type:n}=t;if("server"!==n&&"webassembly"!==n&&"auto"!==n)throw new Error(`Invalid component type '${n}'.`);return t}(a),c=function(e,t,n){const{prerenderId:o}=e;if(o){for(;n.next()&&n.currentElement;){const e=n.currentElement;if(e.nodeType!==Node.COMMENT_NODE)continue;if(!e.textContent)continue;const t=lt.exec(e.textContent),r=t&&t[1];if(r)return ft(r,o),e}throw new Error(`Could not find an end component comment for '${t}'.`)}}(i,n,e);if(t!==i.type)return;switch(i.type){case"webassembly":return r=n,s=c,pt(o=i),{...o,uniqueId:dt++,start:r,end:s};case"server":return function(e,t,n){return ut(e),{...e,uniqueId:dt++,start:t,end:n}}(i,n,c);case"auto":return function(e,t,n){return ut(e),pt(e),{...e,uniqueId:dt++,start:t,end:n}}(i,n,c)}}catch(e){throw new Error(`Found malformed component comment at ${n.textContent}`)}}}let dt=0;function ut(e){const{descriptor:t,sequence:n}=e;if(!t)throw new Error("descriptor must be defined when using a descriptor.");if(void 0===n)throw new Error("sequence must be defined when using a descriptor.");if(!Number.isInteger(n))throw new Error(`Error parsing the sequence '${n}' for component '${JSON.stringify(e)}'`)}function pt(e){const{assembly:t,typeName:n}=e;if(!t)throw new Error("assembly must be defined when using a descriptor.");if(!n)throw new Error("typeName must be defined when using a descriptor.");e.parameterDefinitions=e.parameterDefinitions&&atob(e.parameterDefinitions),e.parameterValues=e.parameterValues&&atob(e.parameterValues)}function ft(e,t){const n=JSON.parse(e);if(1!==Object.keys(n).length)throw new Error(`Invalid end of component comment: '${e}'`);const o=n.prerenderId;if(!o)throw new Error(`End of component comment must have a value for the prerendered property: '${e}'`);if(o!==t)throw new Error(`End of component comment prerendered property must match the start comment prerender id: '${t}', '${o}'`)}class gt{constructor(e){this.childNodes=e,this.currentIndex=-1,this.length=e.length}next(){return this.currentIndex++,this.currentIndex{n+=`0x${e<16?"0":""}${e.toString(16)} `})),n.substr(0,n.length-1)}(e)}'`)):"string"==typeof e&&(n=`String data of length ${e.length}`,t&&(n+=`. Content: '${e}'`)),n}function Et(e){return e&&"undefined"!=typeof ArrayBuffer&&(e instanceof ArrayBuffer||e.constructor&&"ArrayBuffer"===e.constructor.name)}async function Ct(e,t,n,o,r,s){const i={},[a,c]=Tt();i[a]=c,e.log(vt.Trace,`(${t} transport) sending data. ${St(r,s.logMessageContent)}.`);const l=Et(r)?"arraybuffer":"text",h=await n.post(o,{content:r,headers:{...i,...s.headers},responseType:l,timeout:s.timeout,withCredentials:s.withCredentials});e.log(vt.Trace,`(${t} transport) request complete. Response status: ${h.statusCode}.`)}class It{constructor(e,t){this._subject=e,this._observer=t}dispose(){const e=this._subject.observers.indexOf(this._observer);e>-1&&this._subject.observers.splice(e,1),0===this._subject.observers.length&&this._subject.cancelCallback&&this._subject.cancelCallback().catch((e=>{}))}}class kt{constructor(e){this._minLevel=e,this.out=console}log(e,t){if(e>=this._minLevel){const n=`[${(new Date).toISOString()}] ${vt[e]}: ${t}`;switch(e){case vt.Critical:case vt.Error:this.out.error(n);break;case vt.Warning:this.out.warn(n);break;case vt.Information:this.out.info(n);break;default:this.out.log(n)}}}}function Tt(){let e="X-SignalR-User-Agent";return bt.isNode&&(e="User-Agent"),[e,Dt(wt,xt(),bt.isNode?"NodeJS":"Browser",Rt())]}function Dt(e,t,n,o){let r="Microsoft SignalR/";const s=e.split(".");return r+=`${s[0]}.${s[1]}`,r+=` (${e}; `,r+=t&&""!==t?`${t}; `:"Unknown OS; ",r+=`${n}`,r+=o?`; ${o}`:"; Unknown Runtime Version",r+=")",r}function xt(){if(!bt.isNode)return"";switch(process.platform){case"win32":return"Windows NT";case"darwin":return"macOS";case"linux":return"Linux";default:return process.platform}}function Rt(){if(bt.isNode)return process.versions.node}function Pt(e){return e.stack?e.stack:e.message?e.message:`${e}`}class At{writeHandshakeRequest(e){return mt.write(JSON.stringify(e))}parseHandshakeResponse(e){let t,n;if(Et(e)){const o=new Uint8Array(e),r=o.indexOf(mt.RecordSeparatorCode);if(-1===r)throw new Error("Message is incomplete.");const s=r+1;t=String.fromCharCode.apply(null,Array.prototype.slice.call(o.slice(0,s))),n=o.byteLength>s?o.slice(s).buffer:null}else{const o=e,r=o.indexOf(mt.RecordSeparator);if(-1===r)throw new Error("Message is incomplete.");const s=r+1;t=o.substring(0,s),n=o.length>s?o.substring(s):null}const o=mt.parse(t),r=JSON.parse(o[0]);if(r.type)throw new Error("Expected a handshake response from the server.");return[n,r]}}class Nt extends Error{constructor(e,t){const n=new.target.prototype;super(`${e}: Status code '${t}'`),this.statusCode=t,this.__proto__=n}}class Ut extends Error{constructor(e="A timeout occurred."){const t=new.target.prototype;super(e),this.__proto__=t}}class Mt extends Error{constructor(e="An abort occurred."){const t=new.target.prototype;super(e),this.__proto__=t}}class Bt extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="UnsupportedTransportError",this.__proto__=n}}class Lt extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="DisabledTransportError",this.__proto__=n}}class $t extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="FailedToStartTransportError",this.__proto__=n}}class Ot extends Error{constructor(e){const t=new.target.prototype;super(e),this.errorType="FailedToNegotiateWithServerError",this.__proto__=t}}class Ft extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.innerErrors=t,this.__proto__=n}}var Ht,jt;!function(e){e[e.Invocation=1]="Invocation",e[e.StreamItem=2]="StreamItem",e[e.Completion=3]="Completion",e[e.StreamInvocation=4]="StreamInvocation",e[e.CancelInvocation=5]="CancelInvocation",e[e.Ping=6]="Ping",e[e.Close=7]="Close",e[e.Ack=8]="Ack",e[e.Sequence=9]="Sequence"}(Ht||(Ht={}));class Wt{constructor(){this.observers=[]}next(e){for(const t of this.observers)t.next(e)}error(e){for(const t of this.observers)t.error&&t.error(e)}complete(){for(const e of this.observers)e.complete&&e.complete()}subscribe(e){return this.observers.push(e),new It(this,e)}}class zt{constructor(e,t,n){this._bufferSize=1e5,this._messages=[],this._totalMessageCount=0,this._waitForSequenceMessage=!1,this._nextReceivingSequenceId=1,this._latestReceivedSequenceId=0,this._bufferedByteCount=0,this._reconnectInProgress=!1,this._protocol=e,this._connection=t,this._bufferSize=n}async _send(e){const t=this._protocol.writeMessage(e);let n=Promise.resolve();if(this._isInvocationMessage(e)){this._totalMessageCount++;let e=()=>{},o=()=>{};Et(t)?this._bufferedByteCount+=t.byteLength:this._bufferedByteCount+=t.length,this._bufferedByteCount>=this._bufferSize&&(n=new Promise(((t,n)=>{e=t,o=n}))),this._messages.push(new qt(t,this._totalMessageCount,e,o))}try{this._reconnectInProgress||await this._connection.send(t)}catch{this._disconnected()}await n}_ack(e){let t=-1;for(let n=0;nthis._nextReceivingSequenceId?this._connection.stop(new Error("Sequence ID greater than amount of messages we've received.")):this._nextReceivingSequenceId=e.sequenceId}_disconnected(){this._reconnectInProgress=!0,this._waitForSequenceMessage=!0}async _resend(){const e=0!==this._messages.length?this._messages[0]._id:this._totalMessageCount+1;await this._connection.send(this._protocol.writeMessage({type:Ht.Sequence,sequenceId:e}));const t=this._messages;for(const e of t)await this._connection.send(e._message);this._reconnectInProgress=!1}_dispose(e){null!=e||(e=new Error("Unable to reconnect to server."));for(const t of this._messages)t._rejector(e)}_isInvocationMessage(e){switch(e.type){case Ht.Invocation:case Ht.StreamItem:case Ht.Completion:case Ht.StreamInvocation:case Ht.CancelInvocation:return!0;case Ht.Close:case Ht.Sequence:case Ht.Ping:case Ht.Ack:return!1}}_ackTimer(){void 0===this._ackTimerHandle&&(this._ackTimerHandle=setTimeout((async()=>{try{this._reconnectInProgress||await this._connection.send(this._protocol.writeMessage({type:Ht.Ack,sequenceId:this._latestReceivedSequenceId}))}catch{}clearTimeout(this._ackTimerHandle),this._ackTimerHandle=void 0}),1e3))}}class qt{constructor(e,t,n,o){this._message=e,this._id=t,this._resolver=n,this._rejector=o}}!function(e){e.Disconnected="Disconnected",e.Connecting="Connecting",e.Connected="Connected",e.Disconnecting="Disconnecting",e.Reconnecting="Reconnecting"}(jt||(jt={}));class Jt{static create(e,t,n,o,r,s,i){return new Jt(e,t,n,o,r,s,i)}constructor(e,t,n,o,r,s,i){this._nextKeepAlive=0,this._freezeEventListener=()=>{this._logger.log(vt.Warning,"The page is being frozen, this will likely lead to the connection being closed and messages being lost. For more information see the docs at https://learn.microsoft.com/aspnet/core/signalr/javascript-client#bsleep")},_t.isRequired(e,"connection"),_t.isRequired(t,"logger"),_t.isRequired(n,"protocol"),this.serverTimeoutInMilliseconds=null!=r?r:3e4,this.keepAliveIntervalInMilliseconds=null!=s?s:15e3,this._statefulReconnectBufferSize=null!=i?i:1e5,this._logger=t,this._protocol=n,this.connection=e,this._reconnectPolicy=o,this._handshakeProtocol=new At,this.connection.onreceive=e=>this._processIncomingData(e),this.connection.onclose=e=>this._connectionClosed(e),this._callbacks={},this._methods={},this._closedCallbacks=[],this._reconnectingCallbacks=[],this._reconnectedCallbacks=[],this._invocationId=0,this._receivedHandshakeResponse=!1,this._connectionState=jt.Disconnected,this._connectionStarted=!1,this._cachedPingMessage=this._protocol.writeMessage({type:Ht.Ping})}get state(){return this._connectionState}get connectionId(){return this.connection&&this.connection.connectionId||null}get baseUrl(){return this.connection.baseUrl||""}set baseUrl(e){if(this._connectionState!==jt.Disconnected&&this._connectionState!==jt.Reconnecting)throw new Error("The HubConnection must be in the Disconnected or Reconnecting state to change the url.");if(!e)throw new Error("The HubConnection url must be a valid url.");this.connection.baseUrl=e}start(){return this._startPromise=this._startWithStateTransitions(),this._startPromise}async _startWithStateTransitions(){if(this._connectionState!==jt.Disconnected)return Promise.reject(new Error("Cannot start a HubConnection that is not in the 'Disconnected' state."));this._connectionState=jt.Connecting,this._logger.log(vt.Debug,"Starting HubConnection.");try{await this._startInternal(),bt.isBrowser&&window.document.addEventListener("freeze",this._freezeEventListener),this._connectionState=jt.Connected,this._connectionStarted=!0,this._logger.log(vt.Debug,"HubConnection connected successfully.")}catch(e){return this._connectionState=jt.Disconnected,this._logger.log(vt.Debug,`HubConnection failed to start successfully because of error '${e}'.`),Promise.reject(e)}}async _startInternal(){this._stopDuringStartError=void 0,this._receivedHandshakeResponse=!1;const e=new Promise(((e,t)=>{this._handshakeResolver=e,this._handshakeRejecter=t}));await this.connection.start(this._protocol.transferFormat);try{let t=this._protocol.version;this.connection.features.reconnect||(t=1);const n={protocol:this._protocol.name,version:t};if(this._logger.log(vt.Debug,"Sending handshake request."),await this._sendMessage(this._handshakeProtocol.writeHandshakeRequest(n)),this._logger.log(vt.Information,`Using HubProtocol '${this._protocol.name}'.`),this._cleanupTimeout(),this._resetTimeoutPeriod(),this._resetKeepAliveInterval(),await e,this._stopDuringStartError)throw this._stopDuringStartError;!!this.connection.features.reconnect&&(this._messageBuffer=new zt(this._protocol,this.connection,this._statefulReconnectBufferSize),this.connection.features.disconnected=this._messageBuffer._disconnected.bind(this._messageBuffer),this.connection.features.resend=()=>{if(this._messageBuffer)return this._messageBuffer._resend()}),this.connection.features.inherentKeepAlive||await this._sendMessage(this._cachedPingMessage)}catch(e){throw this._logger.log(vt.Debug,`Hub handshake failed with error '${e}' during start(). Stopping HubConnection.`),this._cleanupTimeout(),this._cleanupPingTimer(),await this.connection.stop(e),e}}async stop(){const e=this._startPromise;this.connection.features.reconnect=!1,this._stopPromise=this._stopInternal(),await this._stopPromise;try{await e}catch(e){}}_stopInternal(e){if(this._connectionState===jt.Disconnected)return this._logger.log(vt.Debug,`Call to HubConnection.stop(${e}) ignored because it is already in the disconnected state.`),Promise.resolve();if(this._connectionState===jt.Disconnecting)return this._logger.log(vt.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnecting state.`),this._stopPromise;const t=this._connectionState;return this._connectionState=jt.Disconnecting,this._logger.log(vt.Debug,"Stopping HubConnection."),this._reconnectDelayHandle?(this._logger.log(vt.Debug,"Connection stopped during reconnect delay. Done reconnecting."),clearTimeout(this._reconnectDelayHandle),this._reconnectDelayHandle=void 0,this._completeClose(),Promise.resolve()):(t===jt.Connected&&this._sendCloseMessage(),this._cleanupTimeout(),this._cleanupPingTimer(),this._stopDuringStartError=e||new Mt("The connection was stopped before the hub handshake could complete."),this.connection.stop(e))}async _sendCloseMessage(){try{await this._sendWithProtocol(this._createCloseMessage())}catch{}}stream(e,...t){const[n,o]=this._replaceStreamingParams(t),r=this._createStreamInvocation(e,t,o);let s;const i=new Wt;return i.cancelCallback=()=>{const e=this._createCancelInvocation(r.invocationId);return delete this._callbacks[r.invocationId],s.then((()=>this._sendWithProtocol(e)))},this._callbacks[r.invocationId]=(e,t)=>{t?i.error(t):e&&(e.type===Ht.Completion?e.error?i.error(new Error(e.error)):i.complete():i.next(e.item))},s=this._sendWithProtocol(r).catch((e=>{i.error(e),delete this._callbacks[r.invocationId]})),this._launchStreams(n,s),i}_sendMessage(e){return this._resetKeepAliveInterval(),this.connection.send(e)}_sendWithProtocol(e){return this._messageBuffer?this._messageBuffer._send(e):this._sendMessage(this._protocol.writeMessage(e))}send(e,...t){const[n,o]=this._replaceStreamingParams(t),r=this._sendWithProtocol(this._createInvocation(e,t,!0,o));return this._launchStreams(n,r),r}invoke(e,...t){const[n,o]=this._replaceStreamingParams(t),r=this._createInvocation(e,t,!1,o);return new Promise(((e,t)=>{this._callbacks[r.invocationId]=(n,o)=>{o?t(o):n&&(n.type===Ht.Completion?n.error?t(new Error(n.error)):e(n.result):t(new Error(`Unexpected message type: ${n.type}`)))};const o=this._sendWithProtocol(r).catch((e=>{t(e),delete this._callbacks[r.invocationId]}));this._launchStreams(n,o)}))}on(e,t){e&&t&&(e=e.toLowerCase(),this._methods[e]||(this._methods[e]=[]),-1===this._methods[e].indexOf(t)&&this._methods[e].push(t))}off(e,t){if(!e)return;e=e.toLowerCase();const n=this._methods[e];if(n)if(t){const o=n.indexOf(t);-1!==o&&(n.splice(o,1),0===n.length&&delete this._methods[e])}else delete this._methods[e]}onclose(e){e&&this._closedCallbacks.push(e)}onreconnecting(e){e&&this._reconnectingCallbacks.push(e)}onreconnected(e){e&&this._reconnectedCallbacks.push(e)}_processIncomingData(e){if(this._cleanupTimeout(),this._receivedHandshakeResponse||(e=this._processHandshakeResponse(e),this._receivedHandshakeResponse=!0),e){const t=this._protocol.parseMessages(e,this._logger);for(const e of t)if(!this._messageBuffer||this._messageBuffer._shouldProcessMessage(e))switch(e.type){case Ht.Invocation:this._invokeClientMethod(e);break;case Ht.StreamItem:case Ht.Completion:{const t=this._callbacks[e.invocationId];if(t){e.type===Ht.Completion&&delete this._callbacks[e.invocationId];try{t(e)}catch(e){this._logger.log(vt.Error,`Stream callback threw error: ${Pt(e)}`)}}break}case Ht.Ping:break;case Ht.Close:{this._logger.log(vt.Information,"Close message received from server.");const t=e.error?new Error("Server returned an error on close: "+e.error):void 0;!0===e.allowReconnect?this.connection.stop(t):this._stopPromise=this._stopInternal(t);break}case Ht.Ack:this._messageBuffer&&this._messageBuffer._ack(e);break;case Ht.Sequence:this._messageBuffer&&this._messageBuffer._resetSequence(e);break;default:this._logger.log(vt.Warning,`Invalid message type: ${e.type}.`)}}this._resetTimeoutPeriod()}_processHandshakeResponse(e){let t,n;try{[n,t]=this._handshakeProtocol.parseHandshakeResponse(e)}catch(e){const t="Error parsing handshake response: "+e;this._logger.log(vt.Error,t);const n=new Error(t);throw this._handshakeRejecter(n),n}if(t.error){const e="Server returned handshake error: "+t.error;this._logger.log(vt.Error,e);const n=new Error(e);throw this._handshakeRejecter(n),n}return this._logger.log(vt.Debug,"Server handshake complete."),this._handshakeResolver(),n}_resetKeepAliveInterval(){this.connection.features.inherentKeepAlive||(this._nextKeepAlive=(new Date).getTime()+this.keepAliveIntervalInMilliseconds,this._cleanupPingTimer())}_resetTimeoutPeriod(){if(!(this.connection.features&&this.connection.features.inherentKeepAlive||(this._timeoutHandle=setTimeout((()=>this.serverTimeout()),this.serverTimeoutInMilliseconds),void 0!==this._pingServerHandle))){let e=this._nextKeepAlive-(new Date).getTime();e<0&&(e=0),this._pingServerHandle=setTimeout((async()=>{if(this._connectionState===jt.Connected)try{await this._sendMessage(this._cachedPingMessage)}catch{this._cleanupPingTimer()}}),e)}}serverTimeout(){this.connection.stop(new Error("Server timeout elapsed without receiving a message from the server."))}async _invokeClientMethod(e){const t=e.target.toLowerCase(),n=this._methods[t];if(!n)return this._logger.log(vt.Warning,`No client method with the name '${t}' found.`),void(e.invocationId&&(this._logger.log(vt.Warning,`No result given for '${t}' method and invocation ID '${e.invocationId}'.`),await this._sendWithProtocol(this._createCompletionMessage(e.invocationId,"Client didn't provide a result.",null))));const o=n.slice(),r=!!e.invocationId;let s,i,a;for(const n of o)try{const o=s;s=await n.apply(this,e.arguments),r&&s&&o&&(this._logger.log(vt.Error,`Multiple results provided for '${t}'. Sending error to server.`),a=this._createCompletionMessage(e.invocationId,"Client provided multiple results.",null)),i=void 0}catch(e){i=e,this._logger.log(vt.Error,`A callback for the method '${t}' threw error '${e}'.`)}a?await this._sendWithProtocol(a):r?(i?a=this._createCompletionMessage(e.invocationId,`${i}`,null):void 0!==s?a=this._createCompletionMessage(e.invocationId,null,s):(this._logger.log(vt.Warning,`No result given for '${t}' method and invocation ID '${e.invocationId}'.`),a=this._createCompletionMessage(e.invocationId,"Client didn't provide a result.",null)),await this._sendWithProtocol(a)):s&&this._logger.log(vt.Error,`Result given for '${t}' method but server is not expecting a result.`)}_connectionClosed(e){this._logger.log(vt.Debug,`HubConnection.connectionClosed(${e}) called while in state ${this._connectionState}.`),this._stopDuringStartError=this._stopDuringStartError||e||new Mt("The underlying connection was closed before the hub handshake could complete."),this._handshakeResolver&&this._handshakeResolver(),this._cancelCallbacksWithError(e||new Error("Invocation canceled due to the underlying connection being closed.")),this._cleanupTimeout(),this._cleanupPingTimer(),this._connectionState===jt.Disconnecting?this._completeClose(e):this._connectionState===jt.Connected&&this._reconnectPolicy?this._reconnect(e):this._connectionState===jt.Connected&&this._completeClose(e)}_completeClose(e){if(this._connectionStarted){this._connectionState=jt.Disconnected,this._connectionStarted=!1,this._messageBuffer&&(this._messageBuffer._dispose(null!=e?e:new Error("Connection closed.")),this._messageBuffer=void 0),bt.isBrowser&&window.document.removeEventListener("freeze",this._freezeEventListener);try{this._closedCallbacks.forEach((t=>t.apply(this,[e])))}catch(t){this._logger.log(vt.Error,`An onclose callback called with error '${e}' threw error '${t}'.`)}}}async _reconnect(e){const t=Date.now();let n=0,o=void 0!==e?e:new Error("Attempting to reconnect due to a unknown error."),r=this._getNextRetryDelay(n++,0,o);if(null===r)return this._logger.log(vt.Debug,"Connection not reconnecting because the IRetryPolicy returned null on the first reconnect attempt."),void this._completeClose(e);if(this._connectionState=jt.Reconnecting,e?this._logger.log(vt.Information,`Connection reconnecting because of error '${e}'.`):this._logger.log(vt.Information,"Connection reconnecting."),0!==this._reconnectingCallbacks.length){try{this._reconnectingCallbacks.forEach((t=>t.apply(this,[e])))}catch(t){this._logger.log(vt.Error,`An onreconnecting callback called with error '${e}' threw error '${t}'.`)}if(this._connectionState!==jt.Reconnecting)return void this._logger.log(vt.Debug,"Connection left the reconnecting state in onreconnecting callback. Done reconnecting.")}for(;null!==r;){if(this._logger.log(vt.Information,`Reconnect attempt number ${n} will start in ${r} ms.`),await new Promise((e=>{this._reconnectDelayHandle=setTimeout(e,r)})),this._reconnectDelayHandle=void 0,this._connectionState!==jt.Reconnecting)return void this._logger.log(vt.Debug,"Connection left the reconnecting state during reconnect delay. Done reconnecting.");try{if(await this._startInternal(),this._connectionState=jt.Connected,this._logger.log(vt.Information,"HubConnection reconnected successfully."),0!==this._reconnectedCallbacks.length)try{this._reconnectedCallbacks.forEach((e=>e.apply(this,[this.connection.connectionId])))}catch(e){this._logger.log(vt.Error,`An onreconnected callback called with connectionId '${this.connection.connectionId}; threw error '${e}'.`)}return}catch(e){if(this._logger.log(vt.Information,`Reconnect attempt failed because of error '${e}'.`),this._connectionState!==jt.Reconnecting)return this._logger.log(vt.Debug,`Connection moved to the '${this._connectionState}' from the reconnecting state during reconnect attempt. Done reconnecting.`),void(this._connectionState===jt.Disconnecting&&this._completeClose());o=e instanceof Error?e:new Error(e.toString()),r=this._getNextRetryDelay(n++,Date.now()-t,o)}}this._logger.log(vt.Information,`Reconnect retries have been exhausted after ${Date.now()-t} ms and ${n} failed attempts. Connection disconnecting.`),this._completeClose()}_getNextRetryDelay(e,t,n){try{return this._reconnectPolicy.nextRetryDelayInMilliseconds({elapsedMilliseconds:t,previousRetryCount:e,retryReason:n})}catch(n){return this._logger.log(vt.Error,`IRetryPolicy.nextRetryDelayInMilliseconds(${e}, ${t}) threw error '${n}'.`),null}}_cancelCallbacksWithError(e){const t=this._callbacks;this._callbacks={},Object.keys(t).forEach((n=>{const o=t[n];try{o(null,e)}catch(t){this._logger.log(vt.Error,`Stream 'error' callback called with '${e}' threw error: ${Pt(t)}`)}}))}_cleanupPingTimer(){this._pingServerHandle&&(clearTimeout(this._pingServerHandle),this._pingServerHandle=void 0)}_cleanupTimeout(){this._timeoutHandle&&clearTimeout(this._timeoutHandle)}_createInvocation(e,t,n,o){if(n)return 0!==o.length?{arguments:t,streamIds:o,target:e,type:Ht.Invocation}:{arguments:t,target:e,type:Ht.Invocation};{const n=this._invocationId;return this._invocationId++,0!==o.length?{arguments:t,invocationId:n.toString(),streamIds:o,target:e,type:Ht.Invocation}:{arguments:t,invocationId:n.toString(),target:e,type:Ht.Invocation}}}_launchStreams(e,t){if(0!==e.length){t||(t=Promise.resolve());for(const n in e)e[n].subscribe({complete:()=>{t=t.then((()=>this._sendWithProtocol(this._createCompletionMessage(n))))},error:e=>{let o;o=e instanceof Error?e.message:e&&e.toString?e.toString():"Unknown error",t=t.then((()=>this._sendWithProtocol(this._createCompletionMessage(n,o))))},next:e=>{t=t.then((()=>this._sendWithProtocol(this._createStreamItemMessage(n,e))))}})}}_replaceStreamingParams(e){const t=[],n=[];for(let o=0;o0)&&(t=!1,this._accessToken=await this._accessTokenFactory()),this._setAuthorizationHeader(e);const n=await this._innerClient.send(e);return t&&401===n.statusCode&&this._accessTokenFactory?(this._accessToken=await this._accessTokenFactory(),this._setAuthorizationHeader(e),await this._innerClient.send(e)):n}_setAuthorizationHeader(e){e.headers||(e.headers={}),this._accessToken?e.headers[Xt.Authorization]=`Bearer ${this._accessToken}`:this._accessTokenFactory&&e.headers[Xt.Authorization]&&delete e.headers[Xt.Authorization]}getCookieString(e){return this._innerClient.getCookieString(e)}}class Zt extends Gt{constructor(e){super(),this._logger=e;const t={_fetchType:void 0,_jar:void 0};var o;o=t,"undefined"==typeof fetch&&(o._jar=new(n(628).CookieJar),"undefined"==typeof fetch?o._fetchType=n(200):o._fetchType=fetch,o._fetchType=n(203)(o._fetchType,o._jar),1)?(this._fetchType=t._fetchType,this._jar=t._jar):this._fetchType=fetch.bind(function(){if("undefined"!=typeof globalThis)return globalThis;if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if(void 0!==n.g)return n.g;throw new Error("could not find global")}()),this._abortControllerType=AbortController;const r={_abortControllerType:this._abortControllerType};(function(e){return"undefined"==typeof AbortController&&(e._abortControllerType=n(778),!0)})(r)&&(this._abortControllerType=r._abortControllerType)}async send(e){if(e.abortSignal&&e.abortSignal.aborted)throw new Mt;if(!e.method)throw new Error("No method defined.");if(!e.url)throw new Error("No url defined.");const t=new this._abortControllerType;let n;e.abortSignal&&(e.abortSignal.onabort=()=>{t.abort(),n=new Mt});let o,r=null;if(e.timeout){const o=e.timeout;r=setTimeout((()=>{t.abort(),this._logger.log(vt.Warning,"Timeout from HTTP request."),n=new Ut}),o)}""===e.content&&(e.content=void 0),e.content&&(e.headers=e.headers||{},Et(e.content)?e.headers["Content-Type"]="application/octet-stream":e.headers["Content-Type"]="text/plain;charset=UTF-8");try{o=await this._fetchType(e.url,{body:e.content,cache:"no-cache",credentials:!0===e.withCredentials?"include":"same-origin",headers:{"X-Requested-With":"XMLHttpRequest",...e.headers},method:e.method,mode:"cors",redirect:"follow",signal:t.signal})}catch(e){if(n)throw n;throw this._logger.log(vt.Warning,`Error from HTTP request. ${e}.`),e}finally{r&&clearTimeout(r),e.abortSignal&&(e.abortSignal.onabort=null)}if(!o.ok){const e=await en(o,"text");throw new Nt(e||o.statusText,o.status)}const s=en(o,e.responseType),i=await s;return new Yt(o.status,o.statusText,i)}getCookieString(e){return""}}function en(e,t){let n;switch(t){case"arraybuffer":n=e.arrayBuffer();break;case"text":default:n=e.text();break;case"blob":case"document":case"json":throw new Error(`${t} is not supported.`)}return n}class tn extends Gt{constructor(e){super(),this._logger=e}send(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new Mt):e.method?e.url?new Promise(((t,n)=>{const o=new XMLHttpRequest;o.open(e.method,e.url,!0),o.withCredentials=void 0===e.withCredentials||e.withCredentials,o.setRequestHeader("X-Requested-With","XMLHttpRequest"),""===e.content&&(e.content=void 0),e.content&&(Et(e.content)?o.setRequestHeader("Content-Type","application/octet-stream"):o.setRequestHeader("Content-Type","text/plain;charset=UTF-8"));const r=e.headers;r&&Object.keys(r).forEach((e=>{o.setRequestHeader(e,r[e])})),e.responseType&&(o.responseType=e.responseType),e.abortSignal&&(e.abortSignal.onabort=()=>{o.abort(),n(new Mt)}),e.timeout&&(o.timeout=e.timeout),o.onload=()=>{e.abortSignal&&(e.abortSignal.onabort=null),o.status>=200&&o.status<300?t(new Yt(o.status,o.statusText,o.response||o.responseText)):n(new Nt(o.response||o.responseText||o.statusText,o.status))},o.onerror=()=>{this._logger.log(vt.Warning,`Error from HTTP request. ${o.status}: ${o.statusText}.`),n(new Nt(o.statusText,o.status))},o.ontimeout=()=>{this._logger.log(vt.Warning,"Timeout from HTTP request."),n(new Ut)},o.send(e.content)})):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}}class nn extends Gt{constructor(e){if(super(),"undefined"!=typeof fetch)this._httpClient=new Zt(e);else{if("undefined"==typeof XMLHttpRequest)throw new Error("No usable HttpClient found.");this._httpClient=new tn(e)}}send(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new Mt):e.method?e.url?this._httpClient.send(e):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}getCookieString(e){return this._httpClient.getCookieString(e)}}var on,rn;!function(e){e[e.None=0]="None",e[e.WebSockets=1]="WebSockets",e[e.ServerSentEvents=2]="ServerSentEvents",e[e.LongPolling=4]="LongPolling"}(on||(on={})),function(e){e[e.Text=1]="Text",e[e.Binary=2]="Binary"}(rn||(rn={}));class sn{constructor(){this._isAborted=!1,this.onabort=null}abort(){this._isAborted||(this._isAborted=!0,this.onabort&&this.onabort())}get signal(){return this}get aborted(){return this._isAborted}}class an{get pollAborted(){return this._pollAbort.aborted}constructor(e,t,n){this._httpClient=e,this._logger=t,this._pollAbort=new sn,this._options=n,this._running=!1,this.onreceive=null,this.onclose=null}async connect(e,t){if(_t.isRequired(e,"url"),_t.isRequired(t,"transferFormat"),_t.isIn(t,rn,"transferFormat"),this._url=e,this._logger.log(vt.Trace,"(LongPolling transport) Connecting."),t===rn.Binary&&"undefined"!=typeof XMLHttpRequest&&"string"!=typeof(new XMLHttpRequest).responseType)throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");const[n,o]=Tt(),r={[n]:o,...this._options.headers},s={abortSignal:this._pollAbort.signal,headers:r,timeout:1e5,withCredentials:this._options.withCredentials};t===rn.Binary&&(s.responseType="arraybuffer");const i=`${e}&_=${Date.now()}`;this._logger.log(vt.Trace,`(LongPolling transport) polling: ${i}.`);const a=await this._httpClient.get(i,s);200!==a.statusCode?(this._logger.log(vt.Error,`(LongPolling transport) Unexpected response code: ${a.statusCode}.`),this._closeError=new Nt(a.statusText||"",a.statusCode),this._running=!1):this._running=!0,this._receiving=this._poll(this._url,s)}async _poll(e,t){try{for(;this._running;)try{const n=`${e}&_=${Date.now()}`;this._logger.log(vt.Trace,`(LongPolling transport) polling: ${n}.`);const o=await this._httpClient.get(n,t);204===o.statusCode?(this._logger.log(vt.Information,"(LongPolling transport) Poll terminated by server."),this._running=!1):200!==o.statusCode?(this._logger.log(vt.Error,`(LongPolling transport) Unexpected response code: ${o.statusCode}.`),this._closeError=new Nt(o.statusText||"",o.statusCode),this._running=!1):o.content?(this._logger.log(vt.Trace,`(LongPolling transport) data received. ${St(o.content,this._options.logMessageContent)}.`),this.onreceive&&this.onreceive(o.content)):this._logger.log(vt.Trace,"(LongPolling transport) Poll timed out, reissuing.")}catch(e){this._running?e instanceof Ut?this._logger.log(vt.Trace,"(LongPolling transport) Poll timed out, reissuing."):(this._closeError=e,this._running=!1):this._logger.log(vt.Trace,`(LongPolling transport) Poll errored after shutdown: ${e.message}`)}}finally{this._logger.log(vt.Trace,"(LongPolling transport) Polling complete."),this.pollAborted||this._raiseOnClose()}}async send(e){return this._running?Ct(this._logger,"LongPolling",this._httpClient,this._url,e,this._options):Promise.reject(new Error("Cannot send until the transport is connected"))}async stop(){this._logger.log(vt.Trace,"(LongPolling transport) Stopping polling."),this._running=!1,this._pollAbort.abort();try{await this._receiving,this._logger.log(vt.Trace,`(LongPolling transport) sending DELETE request to ${this._url}.`);const e={},[t,n]=Tt();e[t]=n;const o={headers:{...e,...this._options.headers},timeout:this._options.timeout,withCredentials:this._options.withCredentials};let r;try{await this._httpClient.delete(this._url,o)}catch(e){r=e}r?r instanceof Nt&&(404===r.statusCode?this._logger.log(vt.Trace,"(LongPolling transport) A 404 response was returned from sending a DELETE request."):this._logger.log(vt.Trace,`(LongPolling transport) Error sending a DELETE request: ${r}`)):this._logger.log(vt.Trace,"(LongPolling transport) DELETE request accepted.")}finally{this._logger.log(vt.Trace,"(LongPolling transport) Stop finished."),this._raiseOnClose()}}_raiseOnClose(){if(this.onclose){let e="(LongPolling transport) Firing onclose event.";this._closeError&&(e+=" Error: "+this._closeError),this._logger.log(vt.Trace,e),this.onclose(this._closeError)}}}class cn{constructor(e,t,n,o){this._httpClient=e,this._accessToken=t,this._logger=n,this._options=o,this.onreceive=null,this.onclose=null}async connect(e,t){return _t.isRequired(e,"url"),_t.isRequired(t,"transferFormat"),_t.isIn(t,rn,"transferFormat"),this._logger.log(vt.Trace,"(SSE transport) Connecting."),this._url=e,this._accessToken&&(e+=(e.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(this._accessToken)}`),new Promise(((n,o)=>{let r,s=!1;if(t===rn.Text){if(bt.isBrowser||bt.isWebWorker)r=new this._options.EventSource(e,{withCredentials:this._options.withCredentials});else{const t=this._httpClient.getCookieString(e),n={};n.Cookie=t;const[o,s]=Tt();n[o]=s,r=new this._options.EventSource(e,{withCredentials:this._options.withCredentials,headers:{...n,...this._options.headers}})}try{r.onmessage=e=>{if(this.onreceive)try{this._logger.log(vt.Trace,`(SSE transport) data received. ${St(e.data,this._options.logMessageContent)}.`),this.onreceive(e.data)}catch(e){return void this._close(e)}},r.onerror=e=>{s?this._close():o(new Error("EventSource failed to connect. The connection could not be found on the server, either the connection ID is not present on the server, or a proxy is refusing/buffering the connection. If you have multiple servers check that sticky sessions are enabled."))},r.onopen=()=>{this._logger.log(vt.Information,`SSE connected to ${this._url}`),this._eventSource=r,s=!0,n()}}catch(e){return void o(e)}}else o(new Error("The Server-Sent Events transport only supports the 'Text' transfer format"))}))}async send(e){return this._eventSource?Ct(this._logger,"SSE",this._httpClient,this._url,e,this._options):Promise.reject(new Error("Cannot send until the transport is connected"))}stop(){return this._close(),Promise.resolve()}_close(e){this._eventSource&&(this._eventSource.close(),this._eventSource=void 0,this.onclose&&this.onclose(e))}}class ln{constructor(e,t,n,o,r,s){this._logger=n,this._accessTokenFactory=t,this._logMessageContent=o,this._webSocketConstructor=r,this._httpClient=e,this.onreceive=null,this.onclose=null,this._headers=s}async connect(e,t){let n;return _t.isRequired(e,"url"),_t.isRequired(t,"transferFormat"),_t.isIn(t,rn,"transferFormat"),this._logger.log(vt.Trace,"(WebSockets transport) Connecting."),this._accessTokenFactory&&(n=await this._accessTokenFactory()),new Promise(((o,r)=>{let s;e=e.replace(/^http/,"ws");const i=this._httpClient.getCookieString(e);let a=!1;if(bt.isReactNative){const t={},[o,r]=Tt();t[o]=r,n&&(t[Xt.Authorization]=`Bearer ${n}`),i&&(t[Xt.Cookie]=i),s=new this._webSocketConstructor(e,void 0,{headers:{...t,...this._headers}})}else n&&(e+=(e.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(n)}`);s||(s=new this._webSocketConstructor(e)),t===rn.Binary&&(s.binaryType="arraybuffer"),s.onopen=t=>{this._logger.log(vt.Information,`WebSocket connected to ${e}.`),this._webSocket=s,a=!0,o()},s.onerror=e=>{let t=null;t="undefined"!=typeof ErrorEvent&&e instanceof ErrorEvent?e.error:"There was an error with the transport",this._logger.log(vt.Information,`(WebSockets transport) ${t}.`)},s.onmessage=e=>{if(this._logger.log(vt.Trace,`(WebSockets transport) data received. ${St(e.data,this._logMessageContent)}.`),this.onreceive)try{this.onreceive(e.data)}catch(e){return void this._close(e)}},s.onclose=e=>{if(a)this._close(e);else{let t=null;t="undefined"!=typeof ErrorEvent&&e instanceof ErrorEvent?e.error:"WebSocket failed to connect. The connection could not be found on the server, either the endpoint may not be a SignalR endpoint, the connection ID is not present on the server, or there is a proxy blocking WebSockets. If you have multiple servers check that sticky sessions are enabled.",r(new Error(t))}}}))}send(e){return this._webSocket&&this._webSocket.readyState===this._webSocketConstructor.OPEN?(this._logger.log(vt.Trace,`(WebSockets transport) sending data. ${St(e,this._logMessageContent)}.`),this._webSocket.send(e),Promise.resolve()):Promise.reject("WebSocket is not in the OPEN state")}stop(){return this._webSocket&&this._close(void 0),Promise.resolve()}_close(e){this._webSocket&&(this._webSocket.onclose=()=>{},this._webSocket.onmessage=()=>{},this._webSocket.onerror=()=>{},this._webSocket.close(),this._webSocket=void 0),this._logger.log(vt.Trace,"(WebSockets transport) socket closed."),this.onclose&&(!this._isCloseEvent(e)||!1!==e.wasClean&&1e3===e.code?e instanceof Error?this.onclose(e):this.onclose():this.onclose(new Error(`WebSocket closed with status code: ${e.code} (${e.reason||"no reason given"}).`)))}_isCloseEvent(e){return e&&"boolean"==typeof e.wasClean&&"number"==typeof e.code}}class hn{constructor(e,t={}){if(this._stopPromiseResolver=()=>{},this.features={},this._negotiateVersion=1,_t.isRequired(e,"url"),this._logger=function(e){return void 0===e?new kt(vt.Information):null===e?yt.instance:void 0!==e.log?e:new kt(e)}(t.logger),this.baseUrl=this._resolveUrl(e),(t=t||{}).logMessageContent=void 0!==t.logMessageContent&&t.logMessageContent,"boolean"!=typeof t.withCredentials&&void 0!==t.withCredentials)throw new Error("withCredentials option was not a 'boolean' or 'undefined' value");t.withCredentials=void 0===t.withCredentials||t.withCredentials,t.timeout=void 0===t.timeout?1e5:t.timeout,"undefined"==typeof WebSocket||t.WebSocket||(t.WebSocket=WebSocket),"undefined"==typeof EventSource||t.EventSource||(t.EventSource=EventSource),this._httpClient=new Qt(t.httpClient||new nn(this._logger),t.accessTokenFactory),this._connectionState="Disconnected",this._connectionStarted=!1,this._options=t,this.onreceive=null,this.onclose=null}async start(e){if(e=e||rn.Binary,_t.isIn(e,rn,"transferFormat"),this._logger.log(vt.Debug,`Starting connection with transfer format '${rn[e]}'.`),"Disconnected"!==this._connectionState)return Promise.reject(new Error("Cannot start an HttpConnection that is not in the 'Disconnected' state."));if(this._connectionState="Connecting",this._startInternalPromise=this._startInternal(e),await this._startInternalPromise,"Disconnecting"===this._connectionState){const e="Failed to start the HttpConnection before stop() was called.";return this._logger.log(vt.Error,e),await this._stopPromise,Promise.reject(new Mt(e))}if("Connected"!==this._connectionState){const e="HttpConnection.startInternal completed gracefully but didn't enter the connection into the connected state!";return this._logger.log(vt.Error,e),Promise.reject(new Mt(e))}this._connectionStarted=!0}send(e){return"Connected"!==this._connectionState?Promise.reject(new Error("Cannot send data if the connection is not in the 'Connected' State.")):(this._sendQueue||(this._sendQueue=new dn(this.transport)),this._sendQueue.send(e))}async stop(e){return"Disconnected"===this._connectionState?(this._logger.log(vt.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnected state.`),Promise.resolve()):"Disconnecting"===this._connectionState?(this._logger.log(vt.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnecting state.`),this._stopPromise):(this._connectionState="Disconnecting",this._stopPromise=new Promise((e=>{this._stopPromiseResolver=e})),await this._stopInternal(e),void await this._stopPromise)}async _stopInternal(e){this._stopError=e;try{await this._startInternalPromise}catch(e){}if(this.transport){try{await this.transport.stop()}catch(e){this._logger.log(vt.Error,`HttpConnection.transport.stop() threw error '${e}'.`),this._stopConnection()}this.transport=void 0}else this._logger.log(vt.Debug,"HttpConnection.transport is undefined in HttpConnection.stop() because start() failed.")}async _startInternal(e){let t=this.baseUrl;this._accessTokenFactory=this._options.accessTokenFactory,this._httpClient._accessTokenFactory=this._accessTokenFactory;try{if(this._options.skipNegotiation){if(this._options.transport!==on.WebSockets)throw new Error("Negotiation can only be skipped when using the WebSocket transport directly.");this.transport=this._constructTransport(on.WebSockets),await this._startTransport(t,e)}else{let n=null,o=0;do{if(n=await this._getNegotiationResponse(t),"Disconnecting"===this._connectionState||"Disconnected"===this._connectionState)throw new Mt("The connection was stopped during negotiation.");if(n.error)throw new Error(n.error);if(n.ProtocolVersion)throw new Error("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.");if(n.url&&(t=n.url),n.accessToken){const e=n.accessToken;this._accessTokenFactory=()=>e,this._httpClient._accessToken=e,this._httpClient._accessTokenFactory=void 0}o++}while(n.url&&o<100);if(100===o&&n.url)throw new Error("Negotiate redirection limit exceeded.");await this._createTransport(t,this._options.transport,n,e)}this.transport instanceof an&&(this.features.inherentKeepAlive=!0),"Connecting"===this._connectionState&&(this._logger.log(vt.Debug,"The HttpConnection connected successfully."),this._connectionState="Connected")}catch(e){return this._logger.log(vt.Error,"Failed to start the connection: "+e),this._connectionState="Disconnected",this.transport=void 0,this._stopPromiseResolver(),Promise.reject(e)}}async _getNegotiationResponse(e){const t={},[n,o]=Tt();t[n]=o;const r=this._resolveNegotiateUrl(e);this._logger.log(vt.Debug,`Sending negotiation request: ${r}.`);try{const e=await this._httpClient.post(r,{content:"",headers:{...t,...this._options.headers},timeout:this._options.timeout,withCredentials:this._options.withCredentials});if(200!==e.statusCode)return Promise.reject(new Error(`Unexpected status code returned from negotiate '${e.statusCode}'`));const n=JSON.parse(e.content);return(!n.negotiateVersion||n.negotiateVersion<1)&&(n.connectionToken=n.connectionId),n.useStatefulReconnect&&!0!==this._options._useStatefulReconnect?Promise.reject(new Ot("Client didn't negotiate Stateful Reconnect but the server did.")):n}catch(e){let t="Failed to complete negotiation with the server: "+e;return e instanceof Nt&&404===e.statusCode&&(t+=" Either this is not a SignalR endpoint or there is a proxy blocking the connection."),this._logger.log(vt.Error,t),Promise.reject(new Ot(t))}}_createConnectUrl(e,t){return t?e+(-1===e.indexOf("?")?"?":"&")+`id=${t}`:e}async _createTransport(e,t,n,o){let r=this._createConnectUrl(e,n.connectionToken);if(this._isITransport(t))return this._logger.log(vt.Debug,"Connection was provided an instance of ITransport, using that directly."),this.transport=t,await this._startTransport(r,o),void(this.connectionId=n.connectionId);const s=[],i=n.availableTransports||[];let a=n;for(const n of i){const i=this._resolveTransportOrError(n,t,o,!0===(null==a?void 0:a.useStatefulReconnect));if(i instanceof Error)s.push(`${n.transport} failed:`),s.push(i);else if(this._isITransport(i)){if(this.transport=i,!a){try{a=await this._getNegotiationResponse(e)}catch(e){return Promise.reject(e)}r=this._createConnectUrl(e,a.connectionToken)}try{return await this._startTransport(r,o),void(this.connectionId=a.connectionId)}catch(e){if(this._logger.log(vt.Error,`Failed to start the transport '${n.transport}': ${e}`),a=void 0,s.push(new $t(`${n.transport} failed: ${e}`,on[n.transport])),"Connecting"!==this._connectionState){const e="Failed to select transport before stop() was called.";return this._logger.log(vt.Debug,e),Promise.reject(new Mt(e))}}}}return s.length>0?Promise.reject(new Ft(`Unable to connect to the server with any of the available transports. ${s.join(" ")}`,s)):Promise.reject(new Error("None of the transports supported by the client are supported by the server."))}_constructTransport(e){switch(e){case on.WebSockets:if(!this._options.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new ln(this._httpClient,this._accessTokenFactory,this._logger,this._options.logMessageContent,this._options.WebSocket,this._options.headers||{});case on.ServerSentEvents:if(!this._options.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new cn(this._httpClient,this._httpClient._accessToken,this._logger,this._options);case on.LongPolling:return new an(this._httpClient,this._logger,this._options);default:throw new Error(`Unknown transport: ${e}.`)}}_startTransport(e,t){return this.transport.onreceive=this.onreceive,this.features.reconnect?this.transport.onclose=async n=>{let o=!1;if(this.features.reconnect){try{this.features.disconnected(),await this.transport.connect(e,t),await this.features.resend()}catch{o=!0}o&&this._stopConnection(n)}else this._stopConnection(n)}:this.transport.onclose=e=>this._stopConnection(e),this.transport.connect(e,t)}_resolveTransportOrError(e,t,n,o){const r=on[e.transport];if(null==r)return this._logger.log(vt.Debug,`Skipping transport '${e.transport}' because it is not supported by this client.`),new Error(`Skipping transport '${e.transport}' because it is not supported by this client.`);if(!function(e,t){return!e||0!=(t&e)}(t,r))return this._logger.log(vt.Debug,`Skipping transport '${on[r]}' because it was disabled by the client.`),new Lt(`'${on[r]}' is disabled by the client.`,r);if(!(e.transferFormats.map((e=>rn[e])).indexOf(n)>=0))return this._logger.log(vt.Debug,`Skipping transport '${on[r]}' because it does not support the requested transfer format '${rn[n]}'.`),new Error(`'${on[r]}' does not support ${rn[n]}.`);if(r===on.WebSockets&&!this._options.WebSocket||r===on.ServerSentEvents&&!this._options.EventSource)return this._logger.log(vt.Debug,`Skipping transport '${on[r]}' because it is not supported in your environment.'`),new Bt(`'${on[r]}' is not supported in your environment.`,r);this._logger.log(vt.Debug,`Selecting transport '${on[r]}'.`);try{return this.features.reconnect=r===on.WebSockets?o:void 0,this._constructTransport(r)}catch(e){return e}}_isITransport(e){return e&&"object"==typeof e&&"connect"in e}_stopConnection(e){if(this._logger.log(vt.Debug,`HttpConnection.stopConnection(${e}) called while in state ${this._connectionState}.`),this.transport=void 0,e=this._stopError||e,this._stopError=void 0,"Disconnected"!==this._connectionState){if("Connecting"===this._connectionState)throw this._logger.log(vt.Warning,`Call to HttpConnection.stopConnection(${e}) was ignored because the connection is still in the connecting state.`),new Error(`HttpConnection.stopConnection(${e}) was called while the connection is still in the connecting state.`);if("Disconnecting"===this._connectionState&&this._stopPromiseResolver(),e?this._logger.log(vt.Error,`Connection disconnected with error '${e}'.`):this._logger.log(vt.Information,"Connection disconnected."),this._sendQueue&&(this._sendQueue.stop().catch((e=>{this._logger.log(vt.Error,`TransportSendQueue.stop() threw error '${e}'.`)})),this._sendQueue=void 0),this.connectionId=void 0,this._connectionState="Disconnected",this._connectionStarted){this._connectionStarted=!1;try{this.onclose&&this.onclose(e)}catch(t){this._logger.log(vt.Error,`HttpConnection.onclose(${e}) threw error '${t}'.`)}}}else this._logger.log(vt.Debug,`Call to HttpConnection.stopConnection(${e}) was ignored because the connection is already in the disconnected state.`)}_resolveUrl(e){if(0===e.lastIndexOf("https://",0)||0===e.lastIndexOf("http://",0))return e;if(!bt.isBrowser)throw new Error(`Cannot resolve '${e}'.`);const t=window.document.createElement("a");return t.href=e,this._logger.log(vt.Information,`Normalizing '${e}' to '${t.href}'.`),t.href}_resolveNegotiateUrl(e){const t=new URL(e);t.pathname.endsWith("/")?t.pathname+="negotiate":t.pathname+="/negotiate";const n=new URLSearchParams(t.searchParams);return n.has("negotiateVersion")||n.append("negotiateVersion",this._negotiateVersion.toString()),n.has("useStatefulReconnect")?"true"===n.get("useStatefulReconnect")&&(this._options._useStatefulReconnect=!0):!0===this._options._useStatefulReconnect&&n.append("useStatefulReconnect","true"),t.search=n.toString(),t.toString()}}class dn{constructor(e){this._transport=e,this._buffer=[],this._executing=!0,this._sendBufferedData=new un,this._transportResult=new un,this._sendLoopPromise=this._sendLoop()}send(e){return this._bufferData(e),this._transportResult||(this._transportResult=new un),this._transportResult.promise}stop(){return this._executing=!1,this._sendBufferedData.resolve(),this._sendLoopPromise}_bufferData(e){if(this._buffer.length&&typeof this._buffer[0]!=typeof e)throw new Error(`Expected data to be of type ${typeof this._buffer} but was of type ${typeof e}`);this._buffer.push(e),this._sendBufferedData.resolve()}async _sendLoop(){for(;;){if(await this._sendBufferedData.promise,!this._executing){this._transportResult&&this._transportResult.reject("Connection stopped.");break}this._sendBufferedData=new un;const e=this._transportResult;this._transportResult=void 0;const t="string"==typeof this._buffer[0]?this._buffer.join(""):dn._concatBuffers(this._buffer);this._buffer.length=0;try{await this._transport.send(t),e.resolve()}catch(t){e.reject(t)}}}static _concatBuffers(e){const t=e.map((e=>e.byteLength)).reduce(((e,t)=>e+t)),n=new Uint8Array(t);let o=0;for(const t of e)n.set(new Uint8Array(t),o),o+=t.byteLength;return n.buffer}}class un{constructor(){this.promise=new Promise(((e,t)=>[this._resolver,this._rejecter]=[e,t]))}resolve(){this._resolver()}reject(e){this._rejecter(e)}}class pn{constructor(){this.name="json",this.version=2,this.transferFormat=rn.Text}parseMessages(e,t){if("string"!=typeof e)throw new Error("Invalid input for JSON hub protocol. Expected a string.");if(!e)return[];null===t&&(t=yt.instance);const n=mt.parse(e),o=[];for(const e of n){const n=JSON.parse(e);if("number"!=typeof n.type)throw new Error("Invalid payload.");switch(n.type){case Ht.Invocation:this._isInvocationMessage(n);break;case Ht.StreamItem:this._isStreamItemMessage(n);break;case Ht.Completion:this._isCompletionMessage(n);break;case Ht.Ping:case Ht.Close:break;case Ht.Ack:this._isAckMessage(n);break;case Ht.Sequence:this._isSequenceMessage(n);break;default:t.log(vt.Information,"Unknown message type '"+n.type+"' ignored.");continue}o.push(n)}return o}writeMessage(e){return mt.write(JSON.stringify(e))}_isInvocationMessage(e){this._assertNotEmptyString(e.target,"Invalid payload for Invocation message."),void 0!==e.invocationId&&this._assertNotEmptyString(e.invocationId,"Invalid payload for Invocation message.")}_isStreamItemMessage(e){if(this._assertNotEmptyString(e.invocationId,"Invalid payload for StreamItem message."),void 0===e.item)throw new Error("Invalid payload for StreamItem message.")}_isCompletionMessage(e){if(e.result&&e.error)throw new Error("Invalid payload for Completion message.");!e.result&&e.error&&this._assertNotEmptyString(e.error,"Invalid payload for Completion message."),this._assertNotEmptyString(e.invocationId,"Invalid payload for Completion message.")}_isAckMessage(e){if("number"!=typeof e.sequenceId)throw new Error("Invalid SequenceId for Ack message.")}_isSequenceMessage(e){if("number"!=typeof e.sequenceId)throw new Error("Invalid SequenceId for Sequence message.")}_assertNotEmptyString(e,t){if("string"!=typeof e||""===e)throw new Error(t)}}const fn={trace:vt.Trace,debug:vt.Debug,info:vt.Information,information:vt.Information,warn:vt.Warning,warning:vt.Warning,error:vt.Error,critical:vt.Critical,none:vt.None};class gn{configureLogging(e){if(_t.isRequired(e,"logging"),function(e){return void 0!==e.log}(e))this.logger=e;else if("string"==typeof e){const t=function(e){const t=fn[e.toLowerCase()];if(void 0!==t)return t;throw new Error(`Unknown log level: ${e}`)}(e);this.logger=new kt(t)}else this.logger=new kt(e);return this}withUrl(e,t){return _t.isRequired(e,"url"),_t.isNotEmpty(e,"url"),this.url=e,this.httpConnectionOptions="object"==typeof t?{...this.httpConnectionOptions,...t}:{...this.httpConnectionOptions,transport:t},this}withHubProtocol(e){return _t.isRequired(e,"protocol"),this.protocol=e,this}withAutomaticReconnect(e){if(this.reconnectPolicy)throw new Error("A reconnectPolicy has already been set.");return e?Array.isArray(e)?this.reconnectPolicy=new Vt(e):this.reconnectPolicy=e:this.reconnectPolicy=new Vt,this}withServerTimeout(e){return _t.isRequired(e,"milliseconds"),this._serverTimeoutInMilliseconds=e,this}withKeepAliveInterval(e){return _t.isRequired(e,"milliseconds"),this._keepAliveIntervalInMilliseconds=e,this}withStatefulReconnect(e){return void 0===this.httpConnectionOptions&&(this.httpConnectionOptions={}),this.httpConnectionOptions._useStatefulReconnect=!0,this._statefulReconnectBufferSize=null==e?void 0:e.bufferSize,this}build(){const e=this.httpConnectionOptions||{};if(void 0===e.logger&&(e.logger=this.logger),!this.url)throw new Error("The 'HubConnectionBuilder.withUrl' method must be called before building the connection.");const t=new hn(this.url,e);return Jt.create(t,this.logger||yt.instance,this.protocol||new pn,this.reconnectPolicy,this._serverTimeoutInMilliseconds,this._keepAliveIntervalInMilliseconds,this._statefulReconnectBufferSize)}}var mn;!function(e){e[e.Default=0]="Default",e[e.Server=1]="Server",e[e.WebAssembly=2]="WebAssembly",e[e.WebView=3]="WebView"}(mn||(mn={}));var vn,yn,wn,_n=4294967295;function bn(e,t,n){var o=Math.floor(n/4294967296),r=n;e.setUint32(t,o),e.setUint32(t+4,r)}function Sn(e,t){return 4294967296*e.getInt32(t)+e.getUint32(t+4)}var En=("undefined"==typeof process||"never"!==(null===(vn=null===process||void 0===process?void 0:process.env)||void 0===vn?void 0:vn.TEXT_ENCODING))&&"undefined"!=typeof TextEncoder&&"undefined"!=typeof TextDecoder;function Cn(e){for(var t=e.length,n=0,o=0;o=55296&&r<=56319&&o65535&&(h-=65536,s.push(h>>>10&1023|55296),h=56320|1023&h),s.push(h)}else s.push(a);s.length>=4096&&(i+=String.fromCharCode.apply(String,s),s.length=0)}return s.length>0&&(i+=String.fromCharCode.apply(String,s)),i}var xn,Rn=En?new TextDecoder:null,Pn=En?"undefined"!=typeof process&&"force"!==(null===(wn=null===process||void 0===process?void 0:process.env)||void 0===wn?void 0:wn.TEXT_DECODER)?200:0:_n,An=function(e,t){this.type=e,this.data=t},Nn=(xn=function(e,t){return xn=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},xn(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}xn(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),Un=function(e){function t(n){var o=e.call(this,n)||this,r=Object.create(t.prototype);return Object.setPrototypeOf(o,r),Object.defineProperty(o,"name",{configurable:!0,enumerable:!1,value:t.name}),o}return Nn(t,e),t}(Error),Mn={type:-1,encode:function(e){var t,n,o,r;return e instanceof Date?function(e){var t,n=e.sec,o=e.nsec;if(n>=0&&o>=0&&n<=17179869183){if(0===o&&n<=4294967295){var r=new Uint8Array(4);return(t=new DataView(r.buffer)).setUint32(0,n),r}var s=n/4294967296,i=4294967295&n;return r=new Uint8Array(8),(t=new DataView(r.buffer)).setUint32(0,o<<2|3&s),t.setUint32(4,i),r}return r=new Uint8Array(12),(t=new DataView(r.buffer)).setUint32(0,o),bn(t,4,n),r}((o=1e6*((t=e.getTime())-1e3*(n=Math.floor(t/1e3))),{sec:n+(r=Math.floor(o/1e9)),nsec:o-1e9*r})):null},decode:function(e){var t=function(e){var t=new DataView(e.buffer,e.byteOffset,e.byteLength);switch(e.byteLength){case 4:return{sec:t.getUint32(0),nsec:0};case 8:var n=t.getUint32(0);return{sec:4294967296*(3&n)+t.getUint32(4),nsec:n>>>2};case 12:return{sec:Sn(t,4),nsec:t.getUint32(0)};default:throw new Un("Unrecognized data size for timestamp (expected 4, 8, or 12): ".concat(e.length))}}(e);return new Date(1e3*t.sec+t.nsec/1e6)}},Bn=function(){function e(){this.builtInEncoders=[],this.builtInDecoders=[],this.encoders=[],this.decoders=[],this.register(Mn)}return e.prototype.register=function(e){var t=e.type,n=e.encode,o=e.decode;if(t>=0)this.encoders[t]=n,this.decoders[t]=o;else{var r=1+t;this.builtInEncoders[r]=n,this.builtInDecoders[r]=o}},e.prototype.tryToEncode=function(e,t){for(var n=0;nthis.maxDepth)throw new Error("Too deep objects in depth ".concat(t));null==e?this.encodeNil():"boolean"==typeof e?this.encodeBoolean(e):"number"==typeof e?this.encodeNumber(e):"string"==typeof e?this.encodeString(e):this.encodeObject(e,t)},e.prototype.ensureBufferSizeToWrite=function(e){var t=this.pos+e;this.view.byteLength=0?e<128?this.writeU8(e):e<256?(this.writeU8(204),this.writeU8(e)):e<65536?(this.writeU8(205),this.writeU16(e)):e<4294967296?(this.writeU8(206),this.writeU32(e)):(this.writeU8(207),this.writeU64(e)):e>=-32?this.writeU8(224|e+32):e>=-128?(this.writeU8(208),this.writeI8(e)):e>=-32768?(this.writeU8(209),this.writeI16(e)):e>=-2147483648?(this.writeU8(210),this.writeI32(e)):(this.writeU8(211),this.writeI64(e)):this.forceFloat32?(this.writeU8(202),this.writeF32(e)):(this.writeU8(203),this.writeF64(e))},e.prototype.writeStringHeader=function(e){if(e<32)this.writeU8(160+e);else if(e<256)this.writeU8(217),this.writeU8(e);else if(e<65536)this.writeU8(218),this.writeU16(e);else{if(!(e<4294967296))throw new Error("Too long string: ".concat(e," bytes in UTF-8"));this.writeU8(219),this.writeU32(e)}},e.prototype.encodeString=function(e){if(e.length>kn){var t=Cn(e);this.ensureBufferSizeToWrite(5+t),this.writeStringHeader(t),Tn(e,this.bytes,this.pos),this.pos+=t}else t=Cn(e),this.ensureBufferSizeToWrite(5+t),this.writeStringHeader(t),function(e,t,n){for(var o=e.length,r=n,s=0;s>6&31|192;else{if(i>=55296&&i<=56319&&s>12&15|224,t[r++]=i>>6&63|128):(t[r++]=i>>18&7|240,t[r++]=i>>12&63|128,t[r++]=i>>6&63|128)}t[r++]=63&i|128}else t[r++]=i}}(e,this.bytes,this.pos),this.pos+=t},e.prototype.encodeObject=function(e,t){var n=this.extensionCodec.tryToEncode(e,this.context);if(null!=n)this.encodeExtension(n);else if(Array.isArray(e))this.encodeArray(e,t);else if(ArrayBuffer.isView(e))this.encodeBinary(e);else{if("object"!=typeof e)throw new Error("Unrecognized object: ".concat(Object.prototype.toString.apply(e)));this.encodeMap(e,t)}},e.prototype.encodeBinary=function(e){var t=e.byteLength;if(t<256)this.writeU8(196),this.writeU8(t);else if(t<65536)this.writeU8(197),this.writeU16(t);else{if(!(t<4294967296))throw new Error("Too large binary: ".concat(t));this.writeU8(198),this.writeU32(t)}var n=Ln(e);this.writeU8a(n)},e.prototype.encodeArray=function(e,t){var n=e.length;if(n<16)this.writeU8(144+n);else if(n<65536)this.writeU8(220),this.writeU16(n);else{if(!(n<4294967296))throw new Error("Too large array: ".concat(n));this.writeU8(221),this.writeU32(n)}for(var o=0,r=e;o0&&e<=this.maxKeyLength},e.prototype.find=function(e,t,n){e:for(var o=0,r=this.caches[n-1];o=this.maxLengthPerKey?n[Math.random()*n.length|0]=o:n.push(o)},e.prototype.decode=function(e,t,n){var o=this.find(e,t,n);if(null!=o)return this.hit++,o;this.miss++;var r=Dn(e,t,n),s=Uint8Array.prototype.slice.call(e,t,t+n);return this.store(s,r),r},e}(),Hn=function(e,t){var n,o,r,s,i={label:0,sent:function(){if(1&r[0])throw r[1];return r[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,o&&(r=2&s[0]?o.return:s[0]?o.throw||((r=o.return)&&r.call(o),0):o.next)&&!(r=r.call(o,s[1])).done)return r;switch(o=0,r&&(s=[2&s[0],r.value]),s[0]){case 0:case 1:r=s;break;case 4:return i.label++,{value:s[1],done:!1};case 5:i.label++,o=s[1],s=[0];continue;case 7:s=i.ops.pop(),i.trys.pop();continue;default:if(!((r=(r=i.trys).length>0&&r[r.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!r||s[1]>r[0]&&s[1]=e},e.prototype.createExtraByteError=function(e){var t=this.view,n=this.pos;return new RangeError("Extra ".concat(t.byteLength-n," of ").concat(t.byteLength," byte(s) found at buffer[").concat(e,"]"))},e.prototype.decode=function(e){this.reinitializeState(),this.setBuffer(e);var t=this.doDecodeSync();if(this.hasRemaining(1))throw this.createExtraByteError(this.pos);return t},e.prototype.decodeMulti=function(e){return Hn(this,(function(t){switch(t.label){case 0:this.reinitializeState(),this.setBuffer(e),t.label=1;case 1:return this.hasRemaining(1)?[4,this.doDecodeSync()]:[3,3];case 2:return t.sent(),[3,1];case 3:return[2]}}))},e.prototype.decodeAsync=function(e){var t,n,o,r,s,i,a;return s=this,void 0,a=function(){var s,i,a,c,l,h,d,u;return Hn(this,(function(p){switch(p.label){case 0:s=!1,p.label=1;case 1:p.trys.push([1,6,7,12]),t=jn(e),p.label=2;case 2:return[4,t.next()];case 3:if((n=p.sent()).done)return[3,5];if(a=n.value,s)throw this.createExtraByteError(this.totalPos);this.appendBuffer(a);try{i=this.doDecodeSync(),s=!0}catch(e){if(!(e instanceof Jn))throw e}this.totalPos+=this.pos,p.label=4;case 4:return[3,2];case 5:return[3,12];case 6:return c=p.sent(),o={error:c},[3,12];case 7:return p.trys.push([7,,10,11]),n&&!n.done&&(r=t.return)?[4,r.call(t)]:[3,9];case 8:p.sent(),p.label=9;case 9:return[3,11];case 10:if(o)throw o.error;return[7];case 11:return[7];case 12:if(s){if(this.hasRemaining(1))throw this.createExtraByteError(this.totalPos);return[2,i]}throw h=(l=this).headByte,d=l.pos,u=l.totalPos,new RangeError("Insufficient data in parsing ".concat(On(h)," at ").concat(u," (").concat(d," in the current buffer)"))}}))},new((i=void 0)||(i=Promise))((function(e,t){function n(e){try{r(a.next(e))}catch(e){t(e)}}function o(e){try{r(a.throw(e))}catch(e){t(e)}}function r(t){var r;t.done?e(t.value):(r=t.value,r instanceof i?r:new i((function(e){e(r)}))).then(n,o)}r((a=a.apply(s,[])).next())}))},e.prototype.decodeArrayStream=function(e){return this.decodeMultiAsync(e,!0)},e.prototype.decodeStream=function(e){return this.decodeMultiAsync(e,!1)},e.prototype.decodeMultiAsync=function(e,t){return function(n,o,r){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var s,i=function(){var n,o,r,s,i,a,c,l,h;return Hn(this,(function(d){switch(d.label){case 0:n=t,o=-1,d.label=1;case 1:d.trys.push([1,13,14,19]),r=jn(e),d.label=2;case 2:return[4,Wn(r.next())];case 3:if((s=d.sent()).done)return[3,12];if(i=s.value,t&&0===o)throw this.createExtraByteError(this.totalPos);this.appendBuffer(i),n&&(o=this.readArraySize(),n=!1,this.complete()),d.label=4;case 4:d.trys.push([4,9,,10]),d.label=5;case 5:return[4,Wn(this.doDecodeSync())];case 6:return[4,d.sent()];case 7:return d.sent(),0==--o?[3,8]:[3,5];case 8:return[3,10];case 9:if(!((a=d.sent())instanceof Jn))throw a;return[3,10];case 10:this.totalPos+=this.pos,d.label=11;case 11:return[3,2];case 12:return[3,19];case 13:return c=d.sent(),l={error:c},[3,19];case 14:return d.trys.push([14,,17,18]),s&&!s.done&&(h=r.return)?[4,Wn(h.call(r))]:[3,16];case 15:d.sent(),d.label=16;case 16:return[3,18];case 17:if(l)throw l.error;return[7];case 18:return[7];case 19:return[2]}}))}.apply(n,o||[]),a=[];return s={},c("next"),c("throw"),c("return"),s[Symbol.asyncIterator]=function(){return this},s;function c(e){i[e]&&(s[e]=function(t){return new Promise((function(n,o){a.push([e,t,n,o])>1||l(e,t)}))})}function l(e,t){try{(n=i[e](t)).value instanceof Wn?Promise.resolve(n.value.v).then(h,d):u(a[0][2],n)}catch(e){u(a[0][3],e)}var n}function h(e){l("next",e)}function d(e){l("throw",e)}function u(e,t){e(t),a.shift(),a.length&&l(a[0][0],a[0][1])}}(this,arguments)},e.prototype.doDecodeSync=function(){e:for(;;){var e=this.readHeadByte(),t=void 0;if(e>=224)t=e-256;else if(e<192)if(e<128)t=e;else if(e<144){if(0!=(o=e-128)){this.pushMapState(o),this.complete();continue e}t={}}else if(e<160){if(0!=(o=e-144)){this.pushArrayState(o),this.complete();continue e}t=[]}else{var n=e-160;t=this.decodeUtf8String(n,0)}else if(192===e)t=null;else if(194===e)t=!1;else if(195===e)t=!0;else if(202===e)t=this.readF32();else if(203===e)t=this.readF64();else if(204===e)t=this.readU8();else if(205===e)t=this.readU16();else if(206===e)t=this.readU32();else if(207===e)t=this.readU64();else if(208===e)t=this.readI8();else if(209===e)t=this.readI16();else if(210===e)t=this.readI32();else if(211===e)t=this.readI64();else if(217===e)n=this.lookU8(),t=this.decodeUtf8String(n,1);else if(218===e)n=this.lookU16(),t=this.decodeUtf8String(n,2);else if(219===e)n=this.lookU32(),t=this.decodeUtf8String(n,4);else if(220===e){if(0!==(o=this.readU16())){this.pushArrayState(o),this.complete();continue e}t=[]}else if(221===e){if(0!==(o=this.readU32())){this.pushArrayState(o),this.complete();continue e}t=[]}else if(222===e){if(0!==(o=this.readU16())){this.pushMapState(o),this.complete();continue e}t={}}else if(223===e){if(0!==(o=this.readU32())){this.pushMapState(o),this.complete();continue e}t={}}else if(196===e){var o=this.lookU8();t=this.decodeBinary(o,1)}else if(197===e)o=this.lookU16(),t=this.decodeBinary(o,2);else if(198===e)o=this.lookU32(),t=this.decodeBinary(o,4);else if(212===e)t=this.decodeExtension(1,0);else if(213===e)t=this.decodeExtension(2,0);else if(214===e)t=this.decodeExtension(4,0);else if(215===e)t=this.decodeExtension(8,0);else if(216===e)t=this.decodeExtension(16,0);else if(199===e)o=this.lookU8(),t=this.decodeExtension(o,1);else if(200===e)o=this.lookU16(),t=this.decodeExtension(o,2);else{if(201!==e)throw new Un("Unrecognized type byte: ".concat(On(e)));o=this.lookU32(),t=this.decodeExtension(o,4)}this.complete();for(var r=this.stack;r.length>0;){var s=r[r.length-1];if(0===s.type){if(s.array[s.position]=t,s.position++,s.position!==s.size)continue e;r.pop(),t=s.array}else{if(1===s.type){if("string"!=(i=typeof t)&&"number"!==i)throw new Un("The type of key must be string or number but "+typeof t);if("__proto__"===t)throw new Un("The key __proto__ is not allowed");s.key=t,s.type=2;continue e}if(s.map[s.key]=t,s.readCount++,s.readCount!==s.size){s.key=null,s.type=1;continue e}r.pop(),t=s.map}}return t}var i},e.prototype.readHeadByte=function(){return-1===this.headByte&&(this.headByte=this.readU8()),this.headByte},e.prototype.complete=function(){this.headByte=-1},e.prototype.readArraySize=function(){var e=this.readHeadByte();switch(e){case 220:return this.readU16();case 221:return this.readU32();default:if(e<160)return e-144;throw new Un("Unrecognized array type byte: ".concat(On(e)))}},e.prototype.pushMapState=function(e){if(e>this.maxMapLength)throw new Un("Max length exceeded: map length (".concat(e,") > maxMapLengthLength (").concat(this.maxMapLength,")"));this.stack.push({type:1,size:e,key:null,readCount:0,map:{}})},e.prototype.pushArrayState=function(e){if(e>this.maxArrayLength)throw new Un("Max length exceeded: array length (".concat(e,") > maxArrayLength (").concat(this.maxArrayLength,")"));this.stack.push({type:0,size:e,array:new Array(e),position:0})},e.prototype.decodeUtf8String=function(e,t){var n;if(e>this.maxStrLength)throw new Un("Max length exceeded: UTF-8 byte length (".concat(e,") > maxStrLength (").concat(this.maxStrLength,")"));if(this.bytes.byteLengthPn?function(e,t,n){var o=e.subarray(t,t+n);return Rn.decode(o)}(this.bytes,r,e):Dn(this.bytes,r,e),this.pos+=t+e,o},e.prototype.stateIsMapKey=function(){return this.stack.length>0&&1===this.stack[this.stack.length-1].type},e.prototype.decodeBinary=function(e,t){if(e>this.maxBinLength)throw new Un("Max length exceeded: bin length (".concat(e,") > maxBinLength (").concat(this.maxBinLength,")"));if(!this.hasRemaining(e+t))throw Kn;var n=this.pos+t,o=this.bytes.subarray(n,n+e);return this.pos+=t+e,o},e.prototype.decodeExtension=function(e,t){if(e>this.maxExtLength)throw new Un("Max length exceeded: ext length (".concat(e,") > maxExtLength (").concat(this.maxExtLength,")"));var n=this.view.getInt8(this.pos+t),o=this.decodeBinary(e,t+1);return this.extensionCodec.decode(o,n,this.context)},e.prototype.lookU8=function(){return this.view.getUint8(this.pos)},e.prototype.lookU16=function(){return this.view.getUint16(this.pos)},e.prototype.lookU32=function(){return this.view.getUint32(this.pos)},e.prototype.readU8=function(){var e=this.view.getUint8(this.pos);return this.pos++,e},e.prototype.readI8=function(){var e=this.view.getInt8(this.pos);return this.pos++,e},e.prototype.readU16=function(){var e=this.view.getUint16(this.pos);return this.pos+=2,e},e.prototype.readI16=function(){var e=this.view.getInt16(this.pos);return this.pos+=2,e},e.prototype.readU32=function(){var e=this.view.getUint32(this.pos);return this.pos+=4,e},e.prototype.readI32=function(){var e=this.view.getInt32(this.pos);return this.pos+=4,e},e.prototype.readU64=function(){var e,t,n=(e=this.view,t=this.pos,4294967296*e.getUint32(t)+e.getUint32(t+4));return this.pos+=8,n},e.prototype.readI64=function(){var e=Sn(this.view,this.pos);return this.pos+=8,e},e.prototype.readF32=function(){var e=this.view.getFloat32(this.pos);return this.pos+=4,e},e.prototype.readF64=function(){var e=this.view.getFloat64(this.pos);return this.pos+=8,e},e}();class Yn{static write(e){let t=e.byteLength||e.length;const n=[];do{let e=127&t;t>>=7,t>0&&(e|=128),n.push(e)}while(t>0);t=e.byteLength||e.length;const o=new Uint8Array(n.length+t);return o.set(n,0),o.set(e,n.length),o.buffer}static parse(e){const t=[],n=new Uint8Array(e),o=[0,7,14,21,28];for(let r=0;r7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=r+i+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(r+i,r+i+a):n.subarray(r+i,r+i+a)),r=r+i+a}return t}}const Gn=new Uint8Array([145,Ht.Ping]);class Qn{constructor(e){this.name="messagepack",this.version=2,this.transferFormat=rn.Binary,this._errorResult=1,this._voidResult=2,this._nonVoidResult=3,e=e||{},this._encoder=new $n(e.extensionCodec,e.context,e.maxDepth,e.initialBufferSize,e.sortKeys,e.forceFloat32,e.ignoreUndefined,e.forceIntegerToFloat),this._decoder=new Xn(e.extensionCodec,e.context,e.maxStrLength,e.maxBinLength,e.maxArrayLength,e.maxMapLength,e.maxExtLength)}parseMessages(e,t){if(!(n=e)||"undefined"==typeof ArrayBuffer||!(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer.");var n;null===t&&(t=yt.instance);const o=Yn.parse(e),r=[];for(const e of o){const n=this._parseMessage(e,t);n&&r.push(n)}return r}writeMessage(e){switch(e.type){case Ht.Invocation:return this._writeInvocation(e);case Ht.StreamInvocation:return this._writeStreamInvocation(e);case Ht.StreamItem:return this._writeStreamItem(e);case Ht.Completion:return this._writeCompletion(e);case Ht.Ping:return Yn.write(Gn);case Ht.CancelInvocation:return this._writeCancelInvocation(e);case Ht.Close:return this._writeClose();case Ht.Ack:return this._writeAck(e);case Ht.Sequence:return this._writeSequence(e);default:throw new Error("Invalid message type.")}}_parseMessage(e,t){if(0===e.length)throw new Error("Invalid payload.");const n=this._decoder.decode(e);if(0===n.length||!(n instanceof Array))throw new Error("Invalid payload.");const o=n[0];switch(o){case Ht.Invocation:return this._createInvocationMessage(this._readHeaders(n),n);case Ht.StreamItem:return this._createStreamItemMessage(this._readHeaders(n),n);case Ht.Completion:return this._createCompletionMessage(this._readHeaders(n),n);case Ht.Ping:return this._createPingMessage(n);case Ht.Close:return this._createCloseMessage(n);case Ht.Ack:return this._createAckMessage(n);case Ht.Sequence:return this._createSequenceMessage(n);default:return t.log(vt.Information,"Unknown message type '"+o+"' ignored."),null}}_createCloseMessage(e){if(e.length<2)throw new Error("Invalid payload for Close message.");return{allowReconnect:e.length>=3?e[2]:void 0,error:e[1],type:Ht.Close}}_createPingMessage(e){if(e.length<1)throw new Error("Invalid payload for Ping message.");return{type:Ht.Ping}}_createInvocationMessage(e,t){if(t.length<5)throw new Error("Invalid payload for Invocation message.");const n=t[2];return n?{arguments:t[4],headers:e,invocationId:n,streamIds:[],target:t[3],type:Ht.Invocation}:{arguments:t[4],headers:e,streamIds:[],target:t[3],type:Ht.Invocation}}_createStreamItemMessage(e,t){if(t.length<4)throw new Error("Invalid payload for StreamItem message.");return{headers:e,invocationId:t[2],item:t[3],type:Ht.StreamItem}}_createCompletionMessage(e,t){if(t.length<4)throw new Error("Invalid payload for Completion message.");const n=t[3];if(n!==this._voidResult&&t.length<5)throw new Error("Invalid payload for Completion message.");let o,r;switch(n){case this._errorResult:o=t[4];break;case this._nonVoidResult:r=t[4]}return{error:o,headers:e,invocationId:t[2],result:r,type:Ht.Completion}}_createAckMessage(e){if(e.length<1)throw new Error("Invalid payload for Ack message.");return{sequenceId:e[1],type:Ht.Ack}}_createSequenceMessage(e){if(e.length<1)throw new Error("Invalid payload for Sequence message.");return{sequenceId:e[1],type:Ht.Sequence}}_writeInvocation(e){let t;return t=e.streamIds?this._encoder.encode([Ht.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments,e.streamIds]):this._encoder.encode([Ht.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments]),Yn.write(t.slice())}_writeStreamInvocation(e){let t;return t=e.streamIds?this._encoder.encode([Ht.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments,e.streamIds]):this._encoder.encode([Ht.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments]),Yn.write(t.slice())}_writeStreamItem(e){const t=this._encoder.encode([Ht.StreamItem,e.headers||{},e.invocationId,e.item]);return Yn.write(t.slice())}_writeCompletion(e){const t=e.error?this._errorResult:void 0!==e.result?this._nonVoidResult:this._voidResult;let n;switch(t){case this._errorResult:n=this._encoder.encode([Ht.Completion,e.headers||{},e.invocationId,t,e.error]);break;case this._voidResult:n=this._encoder.encode([Ht.Completion,e.headers||{},e.invocationId,t]);break;case this._nonVoidResult:n=this._encoder.encode([Ht.Completion,e.headers||{},e.invocationId,t,e.result])}return Yn.write(n.slice())}_writeCancelInvocation(e){const t=this._encoder.encode([Ht.CancelInvocation,e.headers||{},e.invocationId]);return Yn.write(t.slice())}_writeClose(){const e=this._encoder.encode([Ht.Close,null]);return Yn.write(e.slice())}_writeAck(e){const t=this._encoder.encode([Ht.Ack,e.sequenceId]);return Yn.write(t.slice())}_writeSequence(e){const t=this._encoder.encode([Ht.Sequence,e.sequenceId]);return Yn.write(t.slice())}_readHeaders(e){const t=e[1];if("object"!=typeof t)throw new Error("Invalid headers.");return t}}const Zn="function"==typeof TextDecoder?new TextDecoder("utf-8"):null,eo=Zn?Zn.decode.bind(Zn):function(e){let t=0;const n=e.length,o=[],r=[];for(;t65535&&(r-=65536,o.push(r>>>10&1023|55296),r=56320|1023&r),o.push(r)}o.length>1024&&(r.push(String.fromCharCode.apply(null,o)),o.length=0)}return r.push(String.fromCharCode.apply(null,o)),r.join("")},to=Math.pow(2,32),no=Math.pow(2,21)-1;function oo(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24}function ro(e,t){return e[t]+(e[t+1]<<8)+(e[t+2]<<16)+(e[t+3]<<24>>>0)}function so(e,t){const n=ro(e,t+4);if(n>no)throw new Error(`Cannot read uint64 with high order part ${n}, because the result would exceed Number.MAX_SAFE_INTEGER.`);return n*to+ro(e,t)}class io{constructor(e){this.batchData=e;const t=new ho(e);this.arrayRangeReader=new uo(e),this.arrayBuilderSegmentReader=new po(e),this.diffReader=new ao(e),this.editReader=new co(e,t),this.frameReader=new lo(e,t)}updatedComponents(){return oo(this.batchData,this.batchData.length-20)}referenceFrames(){return oo(this.batchData,this.batchData.length-16)}disposedComponentIds(){return oo(this.batchData,this.batchData.length-12)}disposedEventHandlerIds(){return oo(this.batchData,this.batchData.length-8)}updatedComponentsEntry(e,t){const n=e+4*t;return oo(this.batchData,n)}referenceFramesEntry(e,t){return e+20*t}disposedComponentIdsEntry(e,t){const n=e+4*t;return oo(this.batchData,n)}disposedEventHandlerIdsEntry(e,t){const n=e+8*t;return so(this.batchData,n)}}class ao{constructor(e){this.batchDataUint8=e}componentId(e){return oo(this.batchDataUint8,e)}edits(e){return e+4}editsEntry(e,t){return e+16*t}}class co{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}editType(e){return oo(this.batchDataUint8,e)}siblingIndex(e){return oo(this.batchDataUint8,e+4)}newTreeIndex(e){return oo(this.batchDataUint8,e+8)}moveToSiblingIndex(e){return oo(this.batchDataUint8,e+8)}removedAttributeName(e){const t=oo(this.batchDataUint8,e+12);return this.stringReader.readString(t)}}class lo{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}frameType(e){return oo(this.batchDataUint8,e)}subtreeLength(e){return oo(this.batchDataUint8,e+4)}elementReferenceCaptureId(e){const t=oo(this.batchDataUint8,e+4);return this.stringReader.readString(t)}componentId(e){return oo(this.batchDataUint8,e+8)}elementName(e){const t=oo(this.batchDataUint8,e+8);return this.stringReader.readString(t)}textContent(e){const t=oo(this.batchDataUint8,e+4);return this.stringReader.readString(t)}markupContent(e){const t=oo(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeName(e){const t=oo(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeValue(e){const t=oo(this.batchDataUint8,e+8);return this.stringReader.readString(t)}attributeEventHandlerId(e){return so(this.batchDataUint8,e+12)}}class ho{constructor(e){this.batchDataUint8=e,this.stringTableStartIndex=oo(e,e.length-4)}readString(e){if(-1===e)return null;{const n=oo(this.batchDataUint8,this.stringTableStartIndex+4*e),o=function(e,t){let n=0,o=0;for(let r=0;r<4;r++){const s=e[t+r];if(n|=(127&s)<this.nextBatchId)return this.fatalError?(this.logger.log(ot.Debug,`Received a new batch ${e} but errored out on a previous batch ${this.nextBatchId-1}`),void await n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())):void this.logger.log(ot.Debug,`Waiting for batch ${this.nextBatchId}. Batch ${e} not processed.`);try{this.nextBatchId++,this.logger.log(ot.Debug,`Applying batch ${e}.`),function(e,t){const n=fe[e];if(!n)throw new Error(`There is no browser renderer with ID ${e}.`);const o=t.arrayRangeReader,r=t.updatedComponents(),s=o.values(r),i=o.count(r),a=t.referenceFrames(),c=o.values(a),l=t.diffReader;for(let e=0;e{e.onclick=function(e){location.reload(),e.preventDefault()}})),document.querySelectorAll("#blazor-error-ui .dismiss").forEach((e=>{e.onclick=function(e){const t=document.querySelector("#blazor-error-ui");t&&(t.style.display="none"),e.preventDefault()}})))}class vo{constructor(t,n,o,r){this._firstUpdate=!0,this._renderingFailed=!1,this._disposed=!1,this._circuitId=void 0,this._applicationState=n,this._componentManager=t,this._options=o,this._logger=r,this._renderQueue=new fo(this._logger),this._dispatcher=e.attachDispatcher(this)}start(){if(this.isDisposedOrDisposing())throw new Error("Cannot start a disposed circuit.");return this._startPromise||(this._startPromise=this.startCore()),this._startPromise}updateRootComponents(e){var t,n;return this._firstUpdate?(this._firstUpdate=!1,null===(t=this._connection)||void 0===t?void 0:t.send("UpdateRootComponents",e,this._applicationState)):null===(n=this._connection)||void 0===n?void 0:n.send("UpdateRootComponents",e,"")}async startCore(){if(this._connection=await this.startConnection(),this._connection.state!==jt.Connected)return!1;const e=JSON.stringify(this._componentManager.initialComponents.map((e=>{return t=e,{...t,start:void 0,end:void 0};var t})));return this._circuitId=await this._connection.invoke("StartCircuit",Ne.getBaseURI(),Ne.getLocationHref(),e,this._applicationState||""),!!this._circuitId}async startConnection(){var e,t;const n=new Qn;n.name="blazorpack";const o=(new gn).withUrl("_blazor").withHubProtocol(n);this._options.configureSignalR(o);const r=o.build();r.on("JS.AttachComponent",((e,t)=>function(e,t,n,o){let r=fe[e];r||(r=new he(e),fe[e]=r),r.attachRootComponentToLogicalElement(n,t,!1)}(mn.Server,this.resolveElement(t,e),e))),r.on("JS.BeginInvokeJS",this._dispatcher.beginInvokeJSFromDotNet.bind(this._dispatcher)),r.on("JS.EndInvokeDotNet",this._dispatcher.endInvokeDotNetFromJS.bind(this._dispatcher)),r.on("JS.ReceiveByteArray",this._dispatcher.receiveByteArray.bind(this._dispatcher)),r.on("JS.BeginTransmitStream",(e=>{const t=new ReadableStream({start:t=>{r.stream("SendDotNetStreamToJS",e).subscribe({next:e=>t.enqueue(e),complete:()=>t.close(),error:e=>t.error(e)})}});this._dispatcher.supplyDotNetStream(e,t)})),r.on("JS.RenderBatch",(async(e,t)=>{var n,o;this._logger.log(vt.Debug,`Received render batch with id ${e} and ${t.byteLength} bytes.`),await this._renderQueue.processBatch(e,t,this._connection),null===(o=(n=this._componentManager).onAfterRenderBatch)||void 0===o||o.call(n,mn.Server)})),r.on("JS.EndLocationChanging",nt._internal.navigationManager.endLocationChanging),r.onclose((e=>!this._disposed&&!this._renderingFailed&&this._options.reconnectionHandler.onConnectionDown(this._options.reconnectionOptions,e))),r.on("JS.Error",(e=>{this._renderingFailed=!0,this.unhandledError(e),mo()}));try{await r.start()}catch(e){if(this.unhandledError(e),"FailedToNegotiateWithServerError"===e.errorType)throw e;mo(),e.innerErrors&&(e.innerErrors.some((e=>"UnsupportedTransportError"===e.errorType&&e.transport===on.WebSockets))?this._logger.log(vt.Error,"Unable to connect, please ensure you are using an updated browser that supports WebSockets."):e.innerErrors.some((e=>"FailedToStartTransportError"===e.errorType&&e.transport===on.WebSockets))?this._logger.log(vt.Error,"Unable to connect, please ensure WebSockets are available. A VPN or proxy may be blocking the connection."):e.innerErrors.some((e=>"DisabledTransportError"===e.errorType&&e.transport===on.LongPolling))&&this._logger.log(vt.Error,"Unable to initiate a SignalR connection to the server. This might be because the server is not configured to support WebSockets. For additional details, visit https://aka.ms/blazor-server-websockets-error."))}return(null===(t=null===(e=r.connection)||void 0===e?void 0:e.features)||void 0===t?void 0:t.inherentKeepAlive)&&this._logger.log(vt.Warning,"Failed to connect via WebSockets, using the Long Polling fallback transport. This may be due to a VPN or proxy blocking the connection. To troubleshoot this, visit https://aka.ms/blazor-server-using-fallback-long-polling."),r}async disconnect(){var e;await(null===(e=this._connection)||void 0===e?void 0:e.stop())}async reconnect(){if(!this._circuitId)throw new Error("Circuit host not initialized.");return this._connection.state===jt.Connected||(this._connection=await this.startConnection(),!!await this._connection.invoke("ConnectCircuit",this._circuitId)&&(this._options.reconnectionHandler.onConnectionUp(),!0))}beginInvokeDotNetFromJS(e,t,n,o,r){this.throwIfDispatchingWhenDisposed(),this._connection.send("BeginInvokeDotNetFromJS",e?e.toString():null,t,n,o||0,r)}endInvokeJSFromDotNet(e,t,n){this.throwIfDispatchingWhenDisposed(),this._connection.send("EndInvokeJSFromDotNet",e,t,n)}sendByteArray(e,t){this.throwIfDispatchingWhenDisposed(),this._connection.send("ReceiveByteArray",e,t)}throwIfDispatchingWhenDisposed(){if(this._disposed)throw new Error("The circuit associated with this dispatcher is no longer available.")}sendLocationChanged(e,t,n){return this._connection.send("OnLocationChanged",e,t,n)}sendLocationChanging(e,t,n,o){return this._connection.send("OnLocationChanging",e,t,n,o)}sendJsDataStream(e,t,n){return function(e,t,n,o){setTimeout((async()=>{let r=5,s=(new Date).valueOf();try{const i=t instanceof Blob?t.size:t.byteLength;let a=0,c=0;for(;a1)await e.send("ReceiveJSDataChunk",n,c,h,null);else{if(!await e.invoke("ReceiveJSDataChunk",n,c,h,null))break;const t=(new Date).valueOf(),o=t-s;s=t,r=Math.max(1,Math.round(500/Math.max(1,o)))}a+=l,c++}}catch(t){await e.send("ReceiveJSDataChunk",n,-1,null,t.toString())}}),0)}(this._connection,e,t,n)}resolveElement(e,t){const n=function(e){const t=f.get(e);if(t)return f.delete(e),t}(e);if(n)return O(n,!0);const o=Number.parseInt(e);if(!Number.isNaN(o))return function(e){const{start:t,end:n}=e,o=t[$];if(o){if(o!==e)throw new Error("The start component comment was already associated with another component descriptor.");return t}const r=t.parentNode;if(!r)throw new Error(`Comment not connected to the DOM ${t.textContent}`);const s=O(r,!0),i=K(s);t[L]=s,t[$]=e;const a=O(t);if(n){const e=K(a),o=Array.prototype.indexOf.call(i,a)+1;let r=null;for(;r!==n;){const n=i.splice(o,1)[0];if(!n)throw new Error("Could not find the end component comment in the parent logical node list");n[L]=t,e.push(n),r=n}}return a}(this._componentManager.resolveRootComponent(o,t));throw new Error(`Invalid sequence number or identifier '${e}'.`)}getRootComponentManager(){return this._componentManager}unhandledError(e){this._logger.log(vt.Error,e),this.disconnect()}getDisconnectFormData(){const e=new FormData,t=this._circuitId;return e.append("circuitId",t),e}didRenderingFail(){return this._renderingFailed}isDisposedOrDisposing(){return void 0!==this._disposePromise}sendDisconnectBeacon(){if(this._disposed)return;const e=this.getDisconnectFormData();this._disposed=navigator.sendBeacon("_blazor/disconnect",e)}dispose(){return this._disposePromise||(this._disposePromise=this.disposeCore()),this._disposePromise}async disposeCore(){var e;if(!this._startPromise)return void(this._disposed=!0);await this._startPromise,this._disposed=!0,null===(e=this._connection)||void 0===e||e.stop();const t=this.getDisconnectFormData();fetch("_blazor/disconnect",{method:"POST",body:t}),function(e){if(!S.delete(e))throw new Error(`Interop methods are not registered for renderer ${e}`)}(mn.Server)}}const yo={configureSignalR:e=>{},logLevel:ot.Warning,reconnectionOptions:{maxRetries:8,retryIntervalMilliseconds:2e4,dialogId:"components-reconnect-modal"}};class wo{constructor(e,t,n,o){this.maxRetries=t,this.document=n,this.logger=o,this.addedToDom=!1,this.modal=this.document.createElement("div"),this.modal.id=e,this.maxRetries=t,this.modal.style.cssText=["position: fixed","top: 0","right: 0","bottom: 0","left: 0","z-index: 1050","display: none","overflow: hidden","background-color: #fff","opacity: 0.8","text-align: center","font-weight: bold","transition: visibility 0s linear 500ms"].join(";"),this.message=this.document.createElement("h5"),this.message.style.cssText="margin-top: 20px",this.button=this.document.createElement("button"),this.button.style.cssText="margin:5px auto 5px",this.button.textContent="Retry";const r=this.document.createElement("a");r.addEventListener("click",(()=>location.reload())),r.textContent="reload",this.reloadParagraph=this.document.createElement("p"),this.reloadParagraph.textContent="Alternatively, ",this.reloadParagraph.appendChild(r),this.modal.appendChild(this.message),this.modal.appendChild(this.button),this.modal.appendChild(this.reloadParagraph),this.loader=this.getLoader(),this.message.after(this.loader),this.button.addEventListener("click",(async()=>{this.show();try{await nt.reconnect()||this.rejected()}catch(e){this.logger.log(ot.Error,e),this.failed()}}))}show(){this.addedToDom||(this.addedToDom=!0,this.document.body.appendChild(this.modal)),this.modal.style.display="block",this.loader.style.display="inline-block",this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.textContent="Attempting to reconnect to the server...",this.modal.style.visibility="hidden",setTimeout((()=>{this.modal.style.visibility="visible"}),0)}update(e){this.message.textContent=`Attempting to reconnect to the server: ${e} of ${this.maxRetries}`}hide(){this.modal.style.display="none"}failed(){this.button.style.display="block",this.reloadParagraph.style.display="none",this.loader.style.display="none";const e=this.document.createTextNode("Reconnection failed. Try "),t=this.document.createElement("a");t.textContent="reloading",t.setAttribute("href",""),t.addEventListener("click",(()=>location.reload()));const n=this.document.createTextNode(" the page if you're unable to reconnect.");this.message.replaceChildren(e,t,n)}rejected(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.loader.style.display="none";const e=this.document.createTextNode("Could not reconnect to the server. "),t=this.document.createElement("a");t.textContent="Reload",t.setAttribute("href",""),t.addEventListener("click",(()=>location.reload()));const n=this.document.createTextNode(" the page to restore functionality.");this.message.replaceChildren(e,t,n)}getLoader(){const e=this.document.createElement("div");return e.style.cssText=["border: 0.3em solid #f3f3f3","border-top: 0.3em solid #3498db","border-radius: 50%","width: 2em","height: 2em","display: inline-block"].join(";"),e.animate([{transform:"rotate(0deg)"},{transform:"rotate(360deg)"}],{duration:2e3,iterations:1/0}),e}}class _o{constructor(e,t,n){this.dialog=e,this.maxRetries=t,this.document=n,this.document=n;const o=this.document.getElementById(_o.MaxRetriesId);o&&(o.innerText=this.maxRetries.toString())}show(){this.removeClasses(),this.dialog.classList.add(_o.ShowClassName)}update(e){const t=this.document.getElementById(_o.CurrentAttemptId);t&&(t.innerText=e.toString())}hide(){this.removeClasses(),this.dialog.classList.add(_o.HideClassName)}failed(){this.removeClasses(),this.dialog.classList.add(_o.FailedClassName)}rejected(){this.removeClasses(),this.dialog.classList.add(_o.RejectedClassName)}removeClasses(){this.dialog.classList.remove(_o.ShowClassName,_o.HideClassName,_o.FailedClassName,_o.RejectedClassName)}}_o.ShowClassName="components-reconnect-show",_o.HideClassName="components-reconnect-hide",_o.FailedClassName="components-reconnect-failed",_o.RejectedClassName="components-reconnect-rejected",_o.MaxRetriesId="components-reconnect-max-retries",_o.CurrentAttemptId="components-reconnect-current-attempt";class bo{constructor(e,t,n){this._currentReconnectionProcess=null,this._logger=e,this._reconnectionDisplay=t,this._reconnectCallback=n||nt.reconnect}onConnectionDown(e,t){if(!this._reconnectionDisplay){const t=document.getElementById(e.dialogId);this._reconnectionDisplay=t?new _o(t,e.maxRetries,document):new wo(e.dialogId,e.maxRetries,document,this._logger)}this._currentReconnectionProcess||(this._currentReconnectionProcess=new So(e,this._logger,this._reconnectCallback,this._reconnectionDisplay))}onConnectionUp(){this._currentReconnectionProcess&&(this._currentReconnectionProcess.dispose(),this._currentReconnectionProcess=null)}}class So{constructor(e,t,n,o){this.logger=t,this.reconnectCallback=n,this.isDisposed=!1,this.reconnectDisplay=o,this.reconnectDisplay.show(),this.attemptPeriodicReconnection(e)}dispose(){this.isDisposed=!0,this.reconnectDisplay.hide()}async attemptPeriodicReconnection(e){for(let t=0;tSo.MaximumFirstRetryInterval?So.MaximumFirstRetryInterval:e.retryIntervalMilliseconds;if(await this.delay(n),this.isDisposed)break;try{return await this.reconnectCallback()?void 0:void this.reconnectDisplay.rejected()}catch(e){this.logger.log(ot.Error,e)}}this.reconnectDisplay.failed()}delay(e){return new Promise((t=>setTimeout(t,e)))}}So.MaximumFirstRetryInterval=3e3;class Eo{constructor(){this.afterStartedCallbacks=[]}async importInitializersAsync(e,t){await Promise.all(e.map((e=>async function(e,n){const o=function(e){const t=document.baseURI;return t.endsWith("/")?`${t}${e}`:`${t}/${e}`}(n),r=await import(o);if(void 0===r)return;const{beforeStart:s,afterStarted:i}=r;return i&&e.afterStartedCallbacks.push(i),s?s(...t):void 0}(this,e))))}async invokeAfterStartedCallbacks(e){await I,await Promise.all(this.afterStartedCallbacks.map((t=>t(e))))}}let Co,Io,ko,To,Do=!1;async function xo(e){if(Do)throw new Error("Blazor Server has already started.");var t;Do=!0,t=document,Co=at(t,it)||"",To=new st(ko.logLevel),Io=new vo(e,Co,ko,To),To.log(ot.Information,"Starting up Blazor server-side application."),nt.reconnect=async()=>!(Io.didRenderingFail()||!await Io.reconnect()&&(To.log(ot.Information,"Reconnection attempt to the circuit was rejected by the server. This may indicate that the associated state is no longer available on the server."),1)),nt.defaultReconnectionHandler=new bo(To),ko.reconnectionHandler=ko.reconnectionHandler||nt.defaultReconnectionHandler,nt._internal.navigationManager.listenForNavigationEvents(((e,t,n)=>Io.sendLocationChanged(e,t,n)),((e,t,n,o)=>Io.sendLocationChanging(e,t,n,o))),nt._internal.forceCloseConnection=()=>Io.disconnect(),nt._internal.sendJSDataStream=(e,t,n)=>Io.sendJsDataStream(e,t,n);const n=await async function(e){const t=await fetch("_blazor/initializers",{method:"GET",credentials:"include",cache:"no-cache"}),n=await t.json(),o=new Eo;return await o.importInitializersAsync(n,[e]),o}(ko);if(!await Io.start())return void To.log(ot.Error,"Failed to start the circuit.");const o=()=>{Io.sendDisconnectBeacon()};nt.disconnect=o,window.addEventListener("unload",o,{capture:!1,once:!0}),To.log(ot.Information,"Blazor server-side application started."),n.invokeAfterStartedCallbacks(nt)}class Ro{constructor(e){this.initialComponents=e}resolveRootComponent(e,t){return this.initialComponents[e]}}class Po{constructor(){this._eventListeners=new Map}static create(e){const t=new Po;return e.addEventListener=t.addEventListener.bind(t),e.removeEventListener=t.removeEventListener.bind(t),t}addEventListener(e,t){let n=this._eventListeners.get(e);n||(n=new Set,this._eventListeners.set(e,n)),n.add(t)}removeEventListener(e,t){var n;null===(n=this._eventListeners.get(e))||void 0===n||n.delete(t)}dispatchEvent(e,t){const n=this._eventListeners.get(e);if(!n)return;const o={...t,type:e};for(const e of n)e(o)}}let Ao=!1;function No(e){if(Ao)throw new Error("Blazor has already started.");Ao=!0,function(e){if(ko)throw new Error("Circuit options have already been configured.");ko=function(e){const t={...yo,...e};return e&&e.reconnectionOptions&&(t.reconnectionOptions={...yo.reconnectionOptions,...e.reconnectionOptions}),t}(e)}(e),Po.create(nt);const t=function(e){return ct(e,"server").sort(((e,t)=>e.sequence-t.sequence))}(document);return xo(new Ro(t))}nt.start=No,window.DotNet=e,document&&document.currentScript&&"false"!==document.currentScript.getAttribute("autostart")&&No()})()})(); \ No newline at end of file +(()=>{var e={778:()=>{},77:()=>{},203:()=>{},200:()=>{},628:()=>{},321:()=>{}},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var s=t[r]={exports:{}};return e[r](s,s.exports,n),s.exports}n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),(()=>{"use strict";var e,t,r;!function(e){const t=[],n="__jsObjectId",r="__dotNetObject",o="__byte[]",s="__dotNetStream",i="__jsStreamReferenceLength";let a,c;class l{constructor(e){this._jsObject=e,this._cachedFunctions=new Map}findFunction(e){const t=this._cachedFunctions.get(e);if(t)return t;let n,r=this._jsObject;if(e.split(".").forEach((t=>{if(!(t in r))throw new Error(`Could not find '${e}' ('${t}' was undefined).`);n=r,r=r[t]})),r instanceof Function)return r=r.bind(n),this._cachedFunctions.set(e,r),r;throw new Error(`The value '${e}' is not a function.`)}getWrappedObject(){return this._jsObject}}const h={0:new l(window)};h[0]._cachedFunctions.set("import",(e=>("string"==typeof e&&e.startsWith("./")&&(e=new URL(e.substr(2),document.baseURI).toString()),import(e))));let d,u=1;function p(e){t.push(e)}function f(e){if(e&&"object"==typeof e){h[u]=new l(e);const t={[n]:u};return u++,t}throw new Error(`Cannot create a JSObjectReference from the value '${e}'.`)}function g(e){let t=-1;if(e instanceof ArrayBuffer&&(e=new Uint8Array(e)),e instanceof Blob)t=e.size;else{if(!(e.buffer instanceof ArrayBuffer))throw new Error("Supplied value is not a typed array or blob.");if(void 0===e.byteLength)throw new Error(`Cannot create a JSStreamReference from the value '${e}' as it doesn't have a byteLength.`);t=e.byteLength}const r={[i]:t};try{const t=f(e);r[n]=t[n]}catch(t){throw new Error(`Cannot create a JSStreamReference from the value '${e}'.`)}return r}function m(e,n){c=e;const r=n?JSON.parse(n,((e,n)=>t.reduce(((t,n)=>n(e,t)),n))):null;return c=void 0,r}function v(){if(void 0===a)throw new Error("No call dispatcher has been set.");if(null===a)throw new Error("There are multiple .NET runtimes present, so a default dispatcher could not be resolved. Use DotNetObject to invoke .NET instance methods.");return a}e.attachDispatcher=function(e){const t=new y(e);return void 0===a?a=t:a&&(a=null),t},e.attachReviver=p,e.invokeMethod=function(e,t,...n){return v().invokeDotNetStaticMethod(e,t,...n)},e.invokeMethodAsync=function(e,t,...n){return v().invokeDotNetStaticMethodAsync(e,t,...n)},e.createJSObjectReference=f,e.createJSStreamReference=g,e.disposeJSObjectReference=function(e){const t=e&&e[n];"number"==typeof t&&b(t)},function(e){e[e.Default=0]="Default",e[e.JSObjectReference=1]="JSObjectReference",e[e.JSStreamReference=2]="JSStreamReference",e[e.JSVoidResult=3]="JSVoidResult"}(d=e.JSCallResultType||(e.JSCallResultType={}));class y{constructor(e){this._dotNetCallDispatcher=e,this._byteArraysToBeRevived=new Map,this._pendingDotNetToJSStreams=new Map,this._pendingAsyncCalls={},this._nextAsyncCallId=1}getDotNetCallDispatcher(){return this._dotNetCallDispatcher}invokeJSFromDotNet(e,t,n,r){const o=m(this,t),s=I(_(e,r)(...o||[]),n);return null==s?null:T(this,s)}beginInvokeJSFromDotNet(e,t,n,r,o){const s=new Promise((e=>{const r=m(this,n);e(_(t,o)(...r||[]))}));e&&s.then((t=>T(this,[e,!0,I(t,r)]))).then((t=>this._dotNetCallDispatcher.endInvokeJSFromDotNet(e,!0,t)),(t=>this._dotNetCallDispatcher.endInvokeJSFromDotNet(e,!1,JSON.stringify([e,!1,w(t)]))))}endInvokeDotNetFromJS(e,t,n){const r=t?m(this,n):new Error(n);this.completePendingCall(parseInt(e,10),t,r)}invokeDotNetStaticMethod(e,t,...n){return this.invokeDotNetMethod(e,t,null,n)}invokeDotNetStaticMethodAsync(e,t,...n){return this.invokeDotNetMethodAsync(e,t,null,n)}invokeDotNetMethod(e,t,n,r){if(this._dotNetCallDispatcher.invokeDotNetFromJS){const o=T(this,r),s=this._dotNetCallDispatcher.invokeDotNetFromJS(e,t,n,o);return s?m(this,s):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeDotNetMethodAsync instead.")}invokeDotNetMethodAsync(e,t,n,r){if(e&&n)throw new Error(`For instance method calls, assemblyName should be null. Received '${e}'.`);const o=this._nextAsyncCallId++,s=new Promise(((e,t)=>{this._pendingAsyncCalls[o]={resolve:e,reject:t}}));try{const s=T(this,r);this._dotNetCallDispatcher.beginInvokeDotNetFromJS(o,e,t,n,s)}catch(e){this.completePendingCall(o,!1,e)}return s}receiveByteArray(e,t){this._byteArraysToBeRevived.set(e,t)}processByteArray(e){const t=this._byteArraysToBeRevived.get(e);return t?(this._byteArraysToBeRevived.delete(e),t):null}supplyDotNetStream(e,t){if(this._pendingDotNetToJSStreams.has(e)){const n=this._pendingDotNetToJSStreams.get(e);this._pendingDotNetToJSStreams.delete(e),n.resolve(t)}else{const n=new C;n.resolve(t),this._pendingDotNetToJSStreams.set(e,n)}}getDotNetStreamPromise(e){let t;if(this._pendingDotNetToJSStreams.has(e))t=this._pendingDotNetToJSStreams.get(e).streamPromise,this._pendingDotNetToJSStreams.delete(e);else{const n=new C;this._pendingDotNetToJSStreams.set(e,n),t=n.streamPromise}return t}completePendingCall(e,t,n){if(!this._pendingAsyncCalls.hasOwnProperty(e))throw new Error(`There is no pending async call with ID ${e}.`);const r=this._pendingAsyncCalls[e];delete this._pendingAsyncCalls[e],t?r.resolve(n):r.reject(n)}}function w(e){return e instanceof Error?`${e.message}\n${e.stack}`:e?e.toString():"null"}function _(e,t){const n=h[t];if(n)return n.findFunction(e);throw new Error(`JS object instance with ID ${t} does not exist (has it been disposed?).`)}function b(e){delete h[e]}e.findJSFunction=_,e.disposeJSObjectReferenceById=b;class S{constructor(e,t){this._id=e,this._callDispatcher=t}invokeMethod(e,...t){return this._callDispatcher.invokeDotNetMethod(null,e,this._id,t)}invokeMethodAsync(e,...t){return this._callDispatcher.invokeDotNetMethodAsync(null,e,this._id,t)}dispose(){this._callDispatcher.invokeDotNetMethodAsync(null,"__Dispose",this._id,null).catch((e=>console.error(e)))}serializeAsArg(){return{[r]:this._id}}}e.DotNetObject=S,p((function(e,t){if(t&&"object"==typeof t){if(t.hasOwnProperty(r))return new S(t[r],c);if(t.hasOwnProperty(n)){const e=t[n],r=h[e];if(r)return r.getWrappedObject();throw new Error(`JS object instance with Id '${e}' does not exist. It may have been disposed.`)}if(t.hasOwnProperty(o)){const e=t[o],n=c.processByteArray(e);if(void 0===n)throw new Error(`Byte array index '${e}' does not exist.`);return n}if(t.hasOwnProperty(s)){const e=t[s],n=c.getDotNetStreamPromise(e);return new E(n)}}return t}));class E{constructor(e){this._streamPromise=e}stream(){return this._streamPromise}async arrayBuffer(){return new Response(await this.stream()).arrayBuffer()}}class C{constructor(){this.streamPromise=new Promise(((e,t)=>{this.resolve=e,this.reject=t}))}}function I(e,t){switch(t){case d.Default:return e;case d.JSObjectReference:return f(e);case d.JSStreamReference:return g(e);case d.JSVoidResult:return null;default:throw new Error(`Invalid JS call result type '${t}'.`)}}let k=0;function T(e,t){k=0,c=e;const n=JSON.stringify(t,D);return c=void 0,n}function D(e,t){if(t instanceof S)return t.serializeAsArg();if(t instanceof Uint8Array){c.getDotNetCallDispatcher().sendByteArray(k,t);const e={[o]:k};return k++,e}return t}}(e||(e={})),function(e){e[e.prependFrame=1]="prependFrame",e[e.removeFrame=2]="removeFrame",e[e.setAttribute=3]="setAttribute",e[e.removeAttribute=4]="removeAttribute",e[e.updateText=5]="updateText",e[e.stepIn=6]="stepIn",e[e.stepOut=7]="stepOut",e[e.updateMarkup=8]="updateMarkup",e[e.permutationListEntry=9]="permutationListEntry",e[e.permutationListEnd=10]="permutationListEnd"}(t||(t={})),function(e){e[e.element=1]="element",e[e.text=2]="text",e[e.attribute=3]="attribute",e[e.component=4]="component",e[e.region=5]="region",e[e.elementReferenceCapture=6]="elementReferenceCapture",e[e.markup=8]="markup",e[e.namedEvent=10]="namedEvent"}(r||(r={}));class o{constructor(e,t){this.componentId=e,this.fieldValue=t}static fromEvent(e,t){const n=t.target;if(n instanceof Element){const t=function(e){return e instanceof HTMLInputElement?e.type&&"checkbox"===e.type.toLowerCase()?{value:e.checked}:{value:e.value}:e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement?{value:e.value}:null}(n);if(t)return new o(e,t.value)}return null}}const s=new Map,i=new Map,a=[];function c(e){return s.get(e)}function l(e){const t=s.get(e);return(null==t?void 0:t.browserEventName)||e}function h(e,t){e.forEach((e=>s.set(e,t)))}function d(e){const t=[];for(let n=0;ne.selected)).map((e=>e.value))}}{const e=function(e){return!!e&&"INPUT"===e.tagName&&"checkbox"===e.getAttribute("type")}(t);return{value:e?!!t.checked:t.value}}}}),h(["copy","cut","paste"],{createEventArgs:e=>({type:e.type})}),h(["drag","dragend","dragenter","dragleave","dragover","dragstart","drop"],{createEventArgs:e=>{return{...u(t=e),dataTransfer:t.dataTransfer?{dropEffect:t.dataTransfer.dropEffect,effectAllowed:t.dataTransfer.effectAllowed,files:Array.from(t.dataTransfer.files).map((e=>e.name)),items:Array.from(t.dataTransfer.items).map((e=>({kind:e.kind,type:e.type}))),types:t.dataTransfer.types}:null};var t}}),h(["focus","blur","focusin","focusout"],{createEventArgs:e=>({type:e.type})}),h(["keydown","keyup","keypress"],{createEventArgs:e=>{return{key:(t=e).key,code:t.code,location:t.location,repeat:t.repeat,ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),h(["contextmenu","click","mouseover","mouseout","mousemove","mousedown","mouseup","mouseleave","mouseenter","dblclick"],{createEventArgs:e=>u(e)}),h(["error"],{createEventArgs:e=>{return{message:(t=e).message,filename:t.filename,lineno:t.lineno,colno:t.colno,type:t.type};var t}}),h(["loadstart","timeout","abort","load","loadend","progress"],{createEventArgs:e=>{return{lengthComputable:(t=e).lengthComputable,loaded:t.loaded,total:t.total,type:t.type};var t}}),h(["touchcancel","touchend","touchmove","touchenter","touchleave","touchstart"],{createEventArgs:e=>{return{detail:(t=e).detail,touches:d(t.touches),targetTouches:d(t.targetTouches),changedTouches:d(t.changedTouches),ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),h(["gotpointercapture","lostpointercapture","pointercancel","pointerdown","pointerenter","pointerleave","pointermove","pointerout","pointerover","pointerup"],{createEventArgs:e=>{return{...u(t=e),pointerId:t.pointerId,width:t.width,height:t.height,pressure:t.pressure,tiltX:t.tiltX,tiltY:t.tiltY,pointerType:t.pointerType,isPrimary:t.isPrimary};var t}}),h(["wheel","mousewheel"],{createEventArgs:e=>{return{...u(t=e),deltaX:t.deltaX,deltaY:t.deltaY,deltaZ:t.deltaZ,deltaMode:t.deltaMode};var t}}),h(["cancel","close","toggle"],{createEventArgs:()=>({})});const p=["date","datetime-local","month","time","week"],f=new Map;let g,m,v=0;const y={async add(e,t,n){if(!n)throw new Error("initialParameters must be an object, even if empty.");const r="__bl-dynamic-root:"+(++v).toString();f.set(r,e);const o=await b().invokeMethodAsync("AddRootComponent",t,r),s=new _(o,m[t]);return await s.setParameters(n),s}};class w{invoke(e){return this._callback(e)}setCallback(t){this._selfJSObjectReference||(this._selfJSObjectReference=e.createJSObjectReference(this)),this._callback=t}getJSObjectReference(){return this._selfJSObjectReference}dispose(){this._selfJSObjectReference&&e.disposeJSObjectReference(this._selfJSObjectReference)}}class _{constructor(e,t){this._jsEventCallbackWrappers=new Map,this._componentId=e;for(const e of t)"eventcallback"===e.type&&this._jsEventCallbackWrappers.set(e.name.toLowerCase(),new w)}setParameters(e){const t={},n=Object.entries(e||{}),r=n.length;for(const[e,r]of n){const n=this._jsEventCallbackWrappers.get(e.toLowerCase());n&&r?(n.setCallback(r),t[e]=n.getJSObjectReference()):t[e]=r}return b().invokeMethodAsync("SetRootComponentParameters",this._componentId,r,t)}async dispose(){if(null!==this._componentId){await b().invokeMethodAsync("RemoveRootComponent",this._componentId),this._componentId=null;for(const e of this._jsEventCallbackWrappers.values())e.dispose()}}}function b(){if(!g)throw new Error("Dynamic root components have not been enabled in this application.");return g}const S=new Map,E=[],C=new Map;function I(t,n,r,o){var s,i;if(S.has(t))throw new Error(`Interop methods are already registered for renderer ${t}`);S.set(t,n),r&&o&&Object.keys(r).length>0&&function(t,n,r){if(g)throw new Error("Dynamic root components have already been enabled.");g=t,m=n;for(const[t,o]of Object.entries(r)){const r=e.findJSFunction(t,0);for(const e of o)r(e,n[e])}}(T(t),r,o),null===(i=null===(s=C.get(t))||void 0===s?void 0:s[0])||void 0===i||i.call(s),function(e){for(const t of E)t(e)}(t)}function k(e,t,n){return D(e,t.eventHandlerId,(()=>T(e).invokeMethodAsync("DispatchEventAsync",t,n)))}function T(e){const t=S.get(e);if(!t)throw new Error(`No interop methods are registered for renderer ${e}`);return t}let D=(e,t,n)=>n();const R=M(["abort","blur","cancel","canplay","canplaythrough","change","close","cuechange","durationchange","emptied","ended","error","focus","load","loadeddata","loadedmetadata","loadend","loadstart","mouseenter","mouseleave","pointerenter","pointerleave","pause","play","playing","progress","ratechange","reset","scroll","seeked","seeking","stalled","submit","suspend","timeupdate","toggle","unload","volumechange","waiting","DOMNodeInsertedIntoDocument","DOMNodeRemovedFromDocument"]),x={submit:!0},A=M(["click","dblclick","mousedown","mousemove","mouseup"]);class P{constructor(e){this.browserRendererId=e,this.afterClickCallbacks=[];const t=++P.nextEventDelegatorId;this.eventsCollectionKey=`_blazorEvents_${t}`,this.eventInfoStore=new N(this.onGlobalEvent.bind(this))}setListener(e,t,n,r){const o=this.getEventHandlerInfosForElement(e,!0),s=o.getHandler(t);if(s)this.eventInfoStore.update(s.eventHandlerId,n);else{const s={element:e,eventName:t,eventHandlerId:n,renderingComponentId:r};this.eventInfoStore.add(s),o.setHandler(t,s)}}getHandler(e){return this.eventInfoStore.get(e)}removeListener(e){const t=this.eventInfoStore.remove(e);if(t){const e=t.element,n=this.getEventHandlerInfosForElement(e,!1);n&&n.removeHandler(t.eventName)}}notifyAfterClick(e){this.afterClickCallbacks.push(e),this.eventInfoStore.addGlobalListener("click")}setStopPropagation(e,t,n){this.getEventHandlerInfosForElement(e,!0).stopPropagation(t,n)}setPreventDefault(e,t,n){this.getEventHandlerInfosForElement(e,!0).preventDefault(t,n)}onGlobalEvent(e){if(!(e.target instanceof Element))return;this.dispatchGlobalEventToAllElements(e.type,e);const t=(n=e.type,i.get(n));var n;t&&t.forEach((t=>this.dispatchGlobalEventToAllElements(t,e))),"click"===e.type&&this.afterClickCallbacks.forEach((t=>t(e)))}dispatchGlobalEventToAllElements(e,t){const n=t.composedPath();let r=n.shift(),s=null,i=!1;const a=Object.prototype.hasOwnProperty.call(R,e);let l=!1;for(;r;){const u=r,p=this.getEventHandlerInfosForElement(u,!1);if(p){const n=p.getHandler(e);if(n&&(h=u,d=t.type,!((h instanceof HTMLButtonElement||h instanceof HTMLInputElement||h instanceof HTMLTextAreaElement||h instanceof HTMLSelectElement)&&Object.prototype.hasOwnProperty.call(A,d)&&h.disabled))){if(!i){const n=c(e);s=(null==n?void 0:n.createEventArgs)?n.createEventArgs(t):{},i=!0}Object.prototype.hasOwnProperty.call(x,t.type)&&t.preventDefault(),k(this.browserRendererId,{eventHandlerId:n.eventHandlerId,eventName:e,eventFieldInfo:o.fromEvent(n.renderingComponentId,t)},s)}p.stopPropagation(e)&&(l=!0),p.preventDefault(e)&&t.preventDefault()}r=a||l?void 0:n.shift()}var h,d}getEventHandlerInfosForElement(e,t){return Object.prototype.hasOwnProperty.call(e,this.eventsCollectionKey)?e[this.eventsCollectionKey]:t?e[this.eventsCollectionKey]=new U:null}}P.nextEventDelegatorId=0;class N{constructor(e){this.globalListener=e,this.infosByEventHandlerId={},this.countByEventName={},a.push(this.handleEventNameAliasAdded.bind(this))}add(e){if(this.infosByEventHandlerId[e.eventHandlerId])throw new Error(`Event ${e.eventHandlerId} is already tracked`);this.infosByEventHandlerId[e.eventHandlerId]=e,this.addGlobalListener(e.eventName)}get(e){return this.infosByEventHandlerId[e]}addGlobalListener(e){if(e=l(e),Object.prototype.hasOwnProperty.call(this.countByEventName,e))this.countByEventName[e]++;else{this.countByEventName[e]=1;const t=Object.prototype.hasOwnProperty.call(R,e);document.addEventListener(e,this.globalListener,t)}}update(e,t){if(Object.prototype.hasOwnProperty.call(this.infosByEventHandlerId,t))throw new Error(`Event ${t} is already tracked`);const n=this.infosByEventHandlerId[e];delete this.infosByEventHandlerId[e],n.eventHandlerId=t,this.infosByEventHandlerId[t]=n}remove(e){const t=this.infosByEventHandlerId[e];if(t){delete this.infosByEventHandlerId[e];const n=l(t.eventName);0==--this.countByEventName[n]&&(delete this.countByEventName[n],document.removeEventListener(n,this.globalListener))}return t}handleEventNameAliasAdded(e,t){if(Object.prototype.hasOwnProperty.call(this.countByEventName,e)){const n=this.countByEventName[e];delete this.countByEventName[e],document.removeEventListener(e,this.globalListener),this.addGlobalListener(t),this.countByEventName[t]+=n-1}}}class U{constructor(){this.handlers={},this.preventDefaultFlags=null,this.stopPropagationFlags=null}getHandler(e){return Object.prototype.hasOwnProperty.call(this.handlers,e)?this.handlers[e]:null}setHandler(e,t){this.handlers[e]=t}removeHandler(e){delete this.handlers[e]}preventDefault(e,t){return void 0!==t&&(this.preventDefaultFlags=this.preventDefaultFlags||{},this.preventDefaultFlags[e]=t),!!this.preventDefaultFlags&&this.preventDefaultFlags[e]}stopPropagation(e,t){return void 0!==t&&(this.stopPropagationFlags=this.stopPropagationFlags||{},this.stopPropagationFlags[e]=t),!!this.stopPropagationFlags&&this.stopPropagationFlags[e]}}function M(e){const t={};return e.forEach((e=>{t[e]=!0})),t}const B=Symbol(),L=Symbol(),$=Symbol();function O(e,t){if(B in e)return e;const n=[];if(e.childNodes.length>0){if(!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");e.childNodes.forEach((t=>{const r=O(t,!0);r[L]=e,n.push(r)}))}return e[B]=n,e}function F(e){const t=K(e);for(;t.length;)W(e,0)}function H(e,t){const n=document.createComment("!");return j(n,e,t),n}function j(e,t,n){const r=e;let o=e;if(B in e){const t=Q(r);if(t!==e){const n=new Range;n.setStartBefore(e),n.setEndAfter(t),o=n.extractContents()}}const s=z(r);if(s){const e=K(s),t=Array.prototype.indexOf.call(e,r);e.splice(t,1),delete r[L]}const i=K(t);if(n0;)W(n,0)}const r=n;r.parentNode.removeChild(r)}function z(e){return e[L]||null}function q(e,t){return K(e)[t]}function J(e){const t=Y(e);return"http://www.w3.org/2000/svg"===t.namespaceURI&&"foreignObject"!==t.tagName}function K(e){return e[B]}function V(e){const t=K(z(e));return t[Array.prototype.indexOf.call(t,e)+1]||null}function X(e,t){const n=K(e);t.forEach((e=>{e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=Q(e.moveRangeStart)})),t.forEach((t=>{const r=document.createComment("marker");t.moveToBeforeMarker=r;const o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):G(r,e)})),t.forEach((e=>{const t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd;let s=r;for(;s;){const e=s.nextSibling;if(n.insertBefore(s,t),s===o)break;s=e}n.removeChild(t)})),t.forEach((e=>{n[e.toSiblingIndex]=e.moveRangeStart}))}function Y(e){if(e instanceof Element||e instanceof DocumentFragment)return e;if(e instanceof Comment)return e.parentNode;throw new Error("Not a valid logical element")}function G(e,t){if(t instanceof Element||t instanceof DocumentFragment)t.appendChild(e);else{if(!(t instanceof Comment))throw new Error(`Cannot append node because the parent is not a valid logical element. Parent: ${t}`);{const n=V(t);n?n.parentNode.insertBefore(e,n):G(e,z(t))}}}function Q(e){if(e instanceof Element||e instanceof DocumentFragment)return e;const t=V(e);if(t)return t.previousSibling;{const t=z(e);return t instanceof Element||t instanceof DocumentFragment?t.lastChild:Q(t)}}function Z(e){return`_bl_${e}`}const ee="__internalId";e.attachReviver(((e,t)=>t&&"object"==typeof t&&Object.prototype.hasOwnProperty.call(t,ee)&&"string"==typeof t[ee]?function(e){const t=`[${Z(e)}]`;return document.querySelector(t)}(t[ee]):t));const te="_blazorDeferredValue";function ne(e){return"select-multiple"===e.type}function re(e,t){e.value=t||""}function oe(e,t){e instanceof HTMLSelectElement?ne(e)?function(e,t){t||(t=[]);for(let n=0;n{ke()&&function(e,t){if(0!==e.button||function(e){return e.ctrlKey||e.shiftKey||e.altKey||e.metaKey}(e))return;if(e.defaultPrevented)return;const n=function(e){const t=!window._blazorDisableComposedPath&&e.composedPath&&e.composedPath();if(t){for(let e=0;e{const t=document.createElement("script");t.textContent=e.textContent,e.getAttributeNames().forEach((n=>{t.setAttribute(n,e.getAttribute(n))})),e.parentNode.replaceChild(t,e)})),ie.content));var i;let a=0;for(;s.firstChild;)j(s.firstChild,o,a++)}applyAttribute(e,t,n,r){const o=e.frameReader,s=o.attributeName(r),i=o.attributeEventHandlerId(r);if(i){const e=fe(s);return void this.eventDelegator.setListener(n,e,i,t)}const a=o.attributeValue(r);this.setOrRemoveAttributeOrProperty(n,s,a)}insertFrameRange(e,t,n,r,o,s,i){const a=r;for(let a=s;a{je(t,e)})},enableNavigationInterception:function(e){if(void 0!==me&&me!==e)throw new Error("Only one interactive runtime may enable navigation interception at a time.");me=e},setHasLocationChangingListeners:function(e,t){const n=Ae.get(e);if(!n)throw new Error(`Renderer with ID '${e}' is not listening for navigation events`);n.hasLocationChangingEventListeners=t},endLocationChanging:function(e,t){Ne&&e===xe&&(Ne(t),Ne=null)},navigateTo:function(e,t){Be(e,t,!0)},refresh:function(e){!e&&Se()?Ee(location.href,!0):location.reload()},getBaseURI:()=>document.baseURI,getLocationHref:()=>location.href,scrollToElement:Me};function Me(e){const t=document.getElementById(e);return!!t&&(t.scrollIntoView(),!0)}function Be(e,t,n=!1){const r=Ce(e);!t.forceLoad&&be(r)?qe()?Le(r,!1,t.replaceHistoryEntry,t.historyEntryState,n):Ee(r,t.replaceHistoryEntry):function(e,t){if(location.href===e){const t=e+"?";history.replaceState(null,"",t),location.replace(e)}else t?location.replace(e):location.href=e}(e,t.replaceHistoryEntry)}async function Le(e,t,n,r=void 0,o=!1){if(Fe(),function(e){const t=e.indexOf("#");return t>-1&&location.href.replace(location.hash,"")===e.substring(0,t)}(e))return void function(e,t,n){$e(e,t,n);const r=e.indexOf("#");r!==e.length-1&&Me(e.substring(r+1))}(e,n,r);const s=ze();(o||!(null==s?void 0:s.hasLocationChangingEventListeners)||await He(e,r,t,s))&&(_e=!0,$e(e,n,r),await je(t))}function $e(e,t,n=void 0){t?history.replaceState({userState:n,_index:Re},"",e):(Re++,history.pushState({userState:n,_index:Re},"",e))}function Oe(e){return new Promise((t=>{const n=Pe;Pe=()=>{Pe=n,t()},history.go(e)}))}function Fe(){Ne&&(Ne(!1),Ne=null)}function He(e,t,n,r){return new Promise((o=>{Fe(),xe++,Ne=o,r.locationChanging(xe,e,t,n)}))}async function je(e,t){const n=null!=t?t:location.href;await Promise.all(Array.from(Ae,(async([t,r])=>{var o,s;s=t,S.has(s)&&await r.locationChanged(n,null===(o=history.state)||void 0===o?void 0:o.userState,e)})))}async function We(e){var t,n;Pe&&qe()&&await Pe(e),Re=null!==(n=null===(t=history.state)||void 0===t?void 0:t._index)&&void 0!==n?n:0}function ze(){const e=Te();if(void 0!==e)return Ae.get(e)}function qe(){return ke()||!Se()}const Je={focus:function(e,t){if(e instanceof HTMLElement)e.focus({preventScroll:t});else{if(!(e instanceof SVGElement))throw new Error("Unable to focus an invalid element.");if(!e.hasAttribute("tabindex"))throw new Error("Unable to focus an SVG element that does not have a tabindex.");e.focus({preventScroll:t})}},focusBySelector:function(e,t){const n=document.querySelector(e);n&&(n.hasAttribute("tabindex")||(n.tabIndex=-1),n.focus({preventScroll:!0}))}},Ke={init:function(e,t,n,r=50){const o=Xe(t);(o||document.documentElement).style.overflowAnchor="none";const s=document.createRange();u(n.parentElement)&&(t.style.display="table-row",n.style.display="table-row");const i=new IntersectionObserver((function(r){r.forEach((r=>{var o;if(!r.isIntersecting)return;s.setStartAfter(t),s.setEndBefore(n);const i=s.getBoundingClientRect().height,a=null===(o=r.rootBounds)||void 0===o?void 0:o.height;r.target===t?e.invokeMethodAsync("OnSpacerBeforeVisible",r.intersectionRect.top-r.boundingClientRect.top,i,a):r.target===n&&n.offsetHeight>0&&e.invokeMethodAsync("OnSpacerAfterVisible",r.boundingClientRect.bottom-r.intersectionRect.bottom,i,a)}))}),{root:o,rootMargin:`${r}px`});i.observe(t),i.observe(n);const a=d(t),c=d(n),{observersByDotNetObjectId:l,id:h}=Ye(e);function d(e){const t={attributes:!0},n=new MutationObserver(((n,r)=>{u(e.parentElement)&&(r.disconnect(),e.style.display="table-row",r.observe(e,t)),i.unobserve(e),i.observe(e)}));return n.observe(e,t),n}function u(e){return null!==e&&(e instanceof HTMLTableElement&&""===e.style.display||"table"===e.style.display||e instanceof HTMLTableSectionElement&&""===e.style.display||"table-row-group"===e.style.display)}l[h]={intersectionObserver:i,mutationObserverBefore:a,mutationObserverAfter:c}},dispose:function(e){const{observersByDotNetObjectId:t,id:n}=Ye(e),r=t[n];r&&(r.intersectionObserver.disconnect(),r.mutationObserverBefore.disconnect(),r.mutationObserverAfter.disconnect(),e.dispose(),delete t[n])}},Ve=Symbol();function Xe(e){return e&&e!==document.body&&e!==document.documentElement?"visible"!==getComputedStyle(e).overflowY?e:Xe(e.parentElement):null}function Ye(e){var t;const n=e._callDispatcher,r=e._id;return null!==(t=n[Ve])&&void 0!==t||(n[Ve]={}),{observersByDotNetObjectId:n[Ve],id:r}}const Ge={getAndRemoveExistingTitle:function(){var e;const t=document.head?document.head.getElementsByTagName("title"):[];if(0===t.length)return null;let n=null;for(let r=t.length-1;r>=0;r--){const o=t[r],s=o.previousSibling;s instanceof Comment&&null!==z(s)||(null===n&&(n=o.textContent),null===(e=o.parentNode)||void 0===e||e.removeChild(o))}return n}},Qe={init:function(e,t){t._blazorInputFileNextFileId=0,t.addEventListener("click",(function(){t.value=""})),t.addEventListener("change",(function(){t._blazorFilesById={};const n=Array.prototype.map.call(t.files,(function(e){const n={id:++t._blazorInputFileNextFileId,lastModified:new Date(e.lastModified).toISOString(),name:e.name,size:e.size,contentType:e.type,readPromise:void 0,arrayBuffer:void 0,blob:e};return t._blazorFilesById[n.id]=n,n}));e.invokeMethodAsync("NotifyChange",n)}))},toImageFile:async function(e,t,n,r,o){const s=Ze(e,t),i=await new Promise((function(e){const t=new Image;t.onload=function(){URL.revokeObjectURL(t.src),e(t)},t.onerror=function(){t.onerror=null,URL.revokeObjectURL(t.src)},t.src=URL.createObjectURL(s.blob)})),a=await new Promise((function(e){var t;const s=Math.min(1,r/i.width),a=Math.min(1,o/i.height),c=Math.min(s,a),l=document.createElement("canvas");l.width=Math.round(i.width*c),l.height=Math.round(i.height*c),null===(t=l.getContext("2d"))||void 0===t||t.drawImage(i,0,0,l.width,l.height),l.toBlob(e,n)})),c={id:++e._blazorInputFileNextFileId,lastModified:s.lastModified,name:s.name,size:(null==a?void 0:a.size)||0,contentType:n,blob:a||s.blob};return e._blazorFilesById[c.id]=c,c},readFileData:async function(e,t){return Ze(e,t).blob}};function Ze(e,t){const n=e._blazorFilesById[t];if(!n)throw new Error(`There is no file with ID ${t}. The file list may have changed. See https://aka.ms/aspnet/blazor-input-file-multiple-selections.`);return n}const et=new Set,tt={enableNavigationPrompt:function(e){0===et.size&&window.addEventListener("beforeunload",nt),et.add(e)},disableNavigationPrompt:function(e){et.delete(e),0===et.size&&window.removeEventListener("beforeunload",nt)}};function nt(e){e.preventDefault(),e.returnValue=!0}async function rt(e,t,n){return e instanceof Blob?await async function(e,t,n){const r=e.slice(t,t+n),o=await r.arrayBuffer();return new Uint8Array(o)}(e,t,n):function(e,t,n){return new Uint8Array(e.buffer,e.byteOffset+t,n)}(e,t,n)}new Map;const ot={navigateTo:function(e,t,n=!1){Be(e,t instanceof Object?t:{forceLoad:t,replaceHistoryEntry:n})},registerCustomEventType:function(e,t){if(!t)throw new Error("The options parameter is required.");if(s.has(e))throw new Error(`The event '${e}' is already registered.`);if(t.browserEventName){const n=i.get(t.browserEventName);n?n.push(e):i.set(t.browserEventName,[e]),a.forEach((n=>n(e,t.browserEventName)))}s.set(e,t)},rootComponents:y,runtime:{},_internal:{navigationManager:Ue,domWrapper:Je,Virtualize:Ke,PageTitle:Ge,InputFile:Qe,NavigationLock:tt,getJSDataStreamChunk:rt,attachWebRendererInterop:I}};var st;function it(e){const t={...at,...e};return e&&e.reconnectionOptions&&(t.reconnectionOptions={...at.reconnectionOptions,...e.reconnectionOptions}),t}window.Blazor=ot,function(e){e[e.Trace=0]="Trace",e[e.Debug=1]="Debug",e[e.Information=2]="Information",e[e.Warning=3]="Warning",e[e.Error=4]="Error",e[e.Critical=5]="Critical",e[e.None=6]="None"}(st||(st={}));const at={configureSignalR:e=>{},logLevel:st.Warning,initializers:void 0,circuitHandlers:[],reconnectionOptions:{maxRetries:8,retryIntervalMilliseconds:2e4,dialogId:"components-reconnect-modal"}};class ct{log(e,t){}}ct.instance=new ct;class lt{constructor(e){this.minLevel=e}log(e,t){if(e>=this.minLevel){const n=`[${(new Date).toISOString()}] ${st[e]}: ${t}`;switch(e){case st.Critical:case st.Error:console.error(n);break;case st.Warning:console.warn(n);break;case st.Information:console.info(n);break;default:console.log(n)}}}}const ht=/^\s*Blazor-Server-Component-State:(?[a-zA-Z0-9+/=]+)$/;function dt(e,t,n="state"){var r;if(e.nodeType===Node.COMMENT_NODE){const o=e.textContent||"",s=t.exec(o),i=s&&s.groups&&s.groups[n];return i&&(null===(r=e.parentNode)||void 0===r||r.removeChild(e)),i}if(!e.hasChildNodes())return;const o=e.childNodes;for(let e=0;e.*)$/);function ft(e,t){const n=e.currentElement;var r,o,s;if(n&&n.nodeType===Node.COMMENT_NODE&&n.textContent){const i=pt.exec(n.textContent),a=i&&i.groups&&i.groups.descriptor;if(!a)return;!function(e){if(e.parentNode instanceof Document)throw new Error("Root components cannot be marked as interactive. The element must be rendered statically so that scripts are not evaluated multiple times.")}(n);try{const i=function(e){const t=JSON.parse(e),{type:n}=t;if("server"!==n&&"webassembly"!==n&&"auto"!==n)throw new Error(`Invalid component type '${n}'.`);return t}(a),c=function(e,t,n){const{prerenderId:r}=e;if(r){for(;n.next()&&n.currentElement;){const e=n.currentElement;if(e.nodeType!==Node.COMMENT_NODE)continue;if(!e.textContent)continue;const t=pt.exec(e.textContent),o=t&&t[1];if(o)return yt(o,r),e}throw new Error(`Could not find an end component comment for '${t}'.`)}}(i,n,e);if(t!==i.type)return;switch(i.type){case"webassembly":return o=n,s=c,vt(r=i),{...r,uniqueId:gt++,start:o,end:s};case"server":return function(e,t,n){return mt(e),{...e,uniqueId:gt++,start:t,end:n}}(i,n,c);case"auto":return function(e,t,n){return mt(e),vt(e),{...e,uniqueId:gt++,start:t,end:n}}(i,n,c)}}catch(e){throw new Error(`Found malformed component comment at ${n.textContent}`)}}}let gt=0;function mt(e){const{descriptor:t,sequence:n}=e;if(!t)throw new Error("descriptor must be defined when using a descriptor.");if(void 0===n)throw new Error("sequence must be defined when using a descriptor.");if(!Number.isInteger(n))throw new Error(`Error parsing the sequence '${n}' for component '${JSON.stringify(e)}'`)}function vt(e){const{assembly:t,typeName:n}=e;if(!t)throw new Error("assembly must be defined when using a descriptor.");if(!n)throw new Error("typeName must be defined when using a descriptor.");e.parameterDefinitions=e.parameterDefinitions&&atob(e.parameterDefinitions),e.parameterValues=e.parameterValues&&atob(e.parameterValues)}function yt(e,t){const n=JSON.parse(e);if(1!==Object.keys(n).length)throw new Error(`Invalid end of component comment: '${e}'`);const r=n.prerenderId;if(!r)throw new Error(`End of component comment must have a value for the prerendered property: '${e}'`);if(r!==t)throw new Error(`End of component comment prerendered property must match the start comment prerender id: '${t}', '${r}'`)}class wt{constructor(e){this.childNodes=e,this.currentIndex=-1,this.length=e.length}next(){return this.currentIndex++,this.currentIndex{n+=`0x${e<16?"0":""}${e.toString(16)} `})),n.substr(0,n.length-1)}(e)}'`)):"string"==typeof e&&(n=`String data of length ${e.length}`,t&&(n+=`. Content: '${e}'`)),n}function Tt(e){return e&&"undefined"!=typeof ArrayBuffer&&(e instanceof ArrayBuffer||e.constructor&&"ArrayBuffer"===e.constructor.name)}async function Dt(e,t,n,r,o,s){const i={},[a,c]=At();i[a]=c,e.log(bt.Trace,`(${t} transport) sending data. ${kt(o,s.logMessageContent)}.`);const l=Tt(o)?"arraybuffer":"text",h=await n.post(r,{content:o,headers:{...i,...s.headers},responseType:l,timeout:s.timeout,withCredentials:s.withCredentials});e.log(bt.Trace,`(${t} transport) request complete. Response status: ${h.statusCode}.`)}class Rt{constructor(e,t){this._subject=e,this._observer=t}dispose(){const e=this._subject.observers.indexOf(this._observer);e>-1&&this._subject.observers.splice(e,1),0===this._subject.observers.length&&this._subject.cancelCallback&&this._subject.cancelCallback().catch((e=>{}))}}class xt{constructor(e){this._minLevel=e,this.out=console}log(e,t){if(e>=this._minLevel){const n=`[${(new Date).toISOString()}] ${bt[e]}: ${t}`;switch(e){case bt.Critical:case bt.Error:this.out.error(n);break;case bt.Warning:this.out.warn(n);break;case bt.Information:this.out.info(n);break;default:this.out.log(n)}}}}function At(){let e="X-SignalR-User-Agent";return It.isNode&&(e="User-Agent"),[e,Pt(Et,Nt(),It.isNode?"NodeJS":"Browser",Ut())]}function Pt(e,t,n,r){let o="Microsoft SignalR/";const s=e.split(".");return o+=`${s[0]}.${s[1]}`,o+=` (${e}; `,o+=t&&""!==t?`${t}; `:"Unknown OS; ",o+=`${n}`,o+=r?`; ${r}`:"; Unknown Runtime Version",o+=")",o}function Nt(){if(!It.isNode)return"";switch(process.platform){case"win32":return"Windows NT";case"darwin":return"macOS";case"linux":return"Linux";default:return process.platform}}function Ut(){if(It.isNode)return process.versions.node}function Mt(e){return e.stack?e.stack:e.message?e.message:`${e}`}class Bt{writeHandshakeRequest(e){return _t.write(JSON.stringify(e))}parseHandshakeResponse(e){let t,n;if(Tt(e)){const r=new Uint8Array(e),o=r.indexOf(_t.RecordSeparatorCode);if(-1===o)throw new Error("Message is incomplete.");const s=o+1;t=String.fromCharCode.apply(null,Array.prototype.slice.call(r.slice(0,s))),n=r.byteLength>s?r.slice(s).buffer:null}else{const r=e,o=r.indexOf(_t.RecordSeparator);if(-1===o)throw new Error("Message is incomplete.");const s=o+1;t=r.substring(0,s),n=r.length>s?r.substring(s):null}const r=_t.parse(t),o=JSON.parse(r[0]);if(o.type)throw new Error("Expected a handshake response from the server.");return[n,o]}}class Lt extends Error{constructor(e,t){const n=new.target.prototype;super(`${e}: Status code '${t}'`),this.statusCode=t,this.__proto__=n}}class $t extends Error{constructor(e="A timeout occurred."){const t=new.target.prototype;super(e),this.__proto__=t}}class Ot extends Error{constructor(e="An abort occurred."){const t=new.target.prototype;super(e),this.__proto__=t}}class Ft extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="UnsupportedTransportError",this.__proto__=n}}class Ht extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="DisabledTransportError",this.__proto__=n}}class jt extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="FailedToStartTransportError",this.__proto__=n}}class Wt extends Error{constructor(e){const t=new.target.prototype;super(e),this.errorType="FailedToNegotiateWithServerError",this.__proto__=t}}class zt extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.innerErrors=t,this.__proto__=n}}var qt,Jt;!function(e){e[e.Invocation=1]="Invocation",e[e.StreamItem=2]="StreamItem",e[e.Completion=3]="Completion",e[e.StreamInvocation=4]="StreamInvocation",e[e.CancelInvocation=5]="CancelInvocation",e[e.Ping=6]="Ping",e[e.Close=7]="Close",e[e.Ack=8]="Ack",e[e.Sequence=9]="Sequence"}(qt||(qt={}));class Kt{constructor(){this.observers=[]}next(e){for(const t of this.observers)t.next(e)}error(e){for(const t of this.observers)t.error&&t.error(e)}complete(){for(const e of this.observers)e.complete&&e.complete()}subscribe(e){return this.observers.push(e),new Rt(this,e)}}class Vt{constructor(e,t,n){this._bufferSize=1e5,this._messages=[],this._totalMessageCount=0,this._waitForSequenceMessage=!1,this._nextReceivingSequenceId=1,this._latestReceivedSequenceId=0,this._bufferedByteCount=0,this._reconnectInProgress=!1,this._protocol=e,this._connection=t,this._bufferSize=n}async _send(e){const t=this._protocol.writeMessage(e);let n=Promise.resolve();if(this._isInvocationMessage(e)){this._totalMessageCount++;let e=()=>{},r=()=>{};Tt(t)?this._bufferedByteCount+=t.byteLength:this._bufferedByteCount+=t.length,this._bufferedByteCount>=this._bufferSize&&(n=new Promise(((t,n)=>{e=t,r=n}))),this._messages.push(new Xt(t,this._totalMessageCount,e,r))}try{this._reconnectInProgress||await this._connection.send(t)}catch{this._disconnected()}await n}_ack(e){let t=-1;for(let n=0;nthis._nextReceivingSequenceId?this._connection.stop(new Error("Sequence ID greater than amount of messages we've received.")):this._nextReceivingSequenceId=e.sequenceId}_disconnected(){this._reconnectInProgress=!0,this._waitForSequenceMessage=!0}async _resend(){const e=0!==this._messages.length?this._messages[0]._id:this._totalMessageCount+1;await this._connection.send(this._protocol.writeMessage({type:qt.Sequence,sequenceId:e}));const t=this._messages;for(const e of t)await this._connection.send(e._message);this._reconnectInProgress=!1}_dispose(e){null!=e||(e=new Error("Unable to reconnect to server."));for(const t of this._messages)t._rejector(e)}_isInvocationMessage(e){switch(e.type){case qt.Invocation:case qt.StreamItem:case qt.Completion:case qt.StreamInvocation:case qt.CancelInvocation:return!0;case qt.Close:case qt.Sequence:case qt.Ping:case qt.Ack:return!1}}_ackTimer(){void 0===this._ackTimerHandle&&(this._ackTimerHandle=setTimeout((async()=>{try{this._reconnectInProgress||await this._connection.send(this._protocol.writeMessage({type:qt.Ack,sequenceId:this._latestReceivedSequenceId}))}catch{}clearTimeout(this._ackTimerHandle),this._ackTimerHandle=void 0}),1e3))}}class Xt{constructor(e,t,n,r){this._message=e,this._id=t,this._resolver=n,this._rejector=r}}!function(e){e.Disconnected="Disconnected",e.Connecting="Connecting",e.Connected="Connected",e.Disconnecting="Disconnecting",e.Reconnecting="Reconnecting"}(Jt||(Jt={}));class Yt{static create(e,t,n,r,o,s,i){return new Yt(e,t,n,r,o,s,i)}constructor(e,t,n,r,o,s,i){this._nextKeepAlive=0,this._freezeEventListener=()=>{this._logger.log(bt.Warning,"The page is being frozen, this will likely lead to the connection being closed and messages being lost. For more information see the docs at https://learn.microsoft.com/aspnet/core/signalr/javascript-client#bsleep")},Ct.isRequired(e,"connection"),Ct.isRequired(t,"logger"),Ct.isRequired(n,"protocol"),this.serverTimeoutInMilliseconds=null!=o?o:3e4,this.keepAliveIntervalInMilliseconds=null!=s?s:15e3,this._statefulReconnectBufferSize=null!=i?i:1e5,this._logger=t,this._protocol=n,this.connection=e,this._reconnectPolicy=r,this._handshakeProtocol=new Bt,this.connection.onreceive=e=>this._processIncomingData(e),this.connection.onclose=e=>this._connectionClosed(e),this._callbacks={},this._methods={},this._closedCallbacks=[],this._reconnectingCallbacks=[],this._reconnectedCallbacks=[],this._invocationId=0,this._receivedHandshakeResponse=!1,this._connectionState=Jt.Disconnected,this._connectionStarted=!1,this._cachedPingMessage=this._protocol.writeMessage({type:qt.Ping})}get state(){return this._connectionState}get connectionId(){return this.connection&&this.connection.connectionId||null}get baseUrl(){return this.connection.baseUrl||""}set baseUrl(e){if(this._connectionState!==Jt.Disconnected&&this._connectionState!==Jt.Reconnecting)throw new Error("The HubConnection must be in the Disconnected or Reconnecting state to change the url.");if(!e)throw new Error("The HubConnection url must be a valid url.");this.connection.baseUrl=e}start(){return this._startPromise=this._startWithStateTransitions(),this._startPromise}async _startWithStateTransitions(){if(this._connectionState!==Jt.Disconnected)return Promise.reject(new Error("Cannot start a HubConnection that is not in the 'Disconnected' state."));this._connectionState=Jt.Connecting,this._logger.log(bt.Debug,"Starting HubConnection.");try{await this._startInternal(),It.isBrowser&&window.document.addEventListener("freeze",this._freezeEventListener),this._connectionState=Jt.Connected,this._connectionStarted=!0,this._logger.log(bt.Debug,"HubConnection connected successfully.")}catch(e){return this._connectionState=Jt.Disconnected,this._logger.log(bt.Debug,`HubConnection failed to start successfully because of error '${e}'.`),Promise.reject(e)}}async _startInternal(){this._stopDuringStartError=void 0,this._receivedHandshakeResponse=!1;const e=new Promise(((e,t)=>{this._handshakeResolver=e,this._handshakeRejecter=t}));await this.connection.start(this._protocol.transferFormat);try{let t=this._protocol.version;this.connection.features.reconnect||(t=1);const n={protocol:this._protocol.name,version:t};if(this._logger.log(bt.Debug,"Sending handshake request."),await this._sendMessage(this._handshakeProtocol.writeHandshakeRequest(n)),this._logger.log(bt.Information,`Using HubProtocol '${this._protocol.name}'.`),this._cleanupTimeout(),this._resetTimeoutPeriod(),this._resetKeepAliveInterval(),await e,this._stopDuringStartError)throw this._stopDuringStartError;!!this.connection.features.reconnect&&(this._messageBuffer=new Vt(this._protocol,this.connection,this._statefulReconnectBufferSize),this.connection.features.disconnected=this._messageBuffer._disconnected.bind(this._messageBuffer),this.connection.features.resend=()=>{if(this._messageBuffer)return this._messageBuffer._resend()}),this.connection.features.inherentKeepAlive||await this._sendMessage(this._cachedPingMessage)}catch(e){throw this._logger.log(bt.Debug,`Hub handshake failed with error '${e}' during start(). Stopping HubConnection.`),this._cleanupTimeout(),this._cleanupPingTimer(),await this.connection.stop(e),e}}async stop(){const e=this._startPromise;this.connection.features.reconnect=!1,this._stopPromise=this._stopInternal(),await this._stopPromise;try{await e}catch(e){}}_stopInternal(e){if(this._connectionState===Jt.Disconnected)return this._logger.log(bt.Debug,`Call to HubConnection.stop(${e}) ignored because it is already in the disconnected state.`),Promise.resolve();if(this._connectionState===Jt.Disconnecting)return this._logger.log(bt.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnecting state.`),this._stopPromise;const t=this._connectionState;return this._connectionState=Jt.Disconnecting,this._logger.log(bt.Debug,"Stopping HubConnection."),this._reconnectDelayHandle?(this._logger.log(bt.Debug,"Connection stopped during reconnect delay. Done reconnecting."),clearTimeout(this._reconnectDelayHandle),this._reconnectDelayHandle=void 0,this._completeClose(),Promise.resolve()):(t===Jt.Connected&&this._sendCloseMessage(),this._cleanupTimeout(),this._cleanupPingTimer(),this._stopDuringStartError=e||new Ot("The connection was stopped before the hub handshake could complete."),this.connection.stop(e))}async _sendCloseMessage(){try{await this._sendWithProtocol(this._createCloseMessage())}catch{}}stream(e,...t){const[n,r]=this._replaceStreamingParams(t),o=this._createStreamInvocation(e,t,r);let s;const i=new Kt;return i.cancelCallback=()=>{const e=this._createCancelInvocation(o.invocationId);return delete this._callbacks[o.invocationId],s.then((()=>this._sendWithProtocol(e)))},this._callbacks[o.invocationId]=(e,t)=>{t?i.error(t):e&&(e.type===qt.Completion?e.error?i.error(new Error(e.error)):i.complete():i.next(e.item))},s=this._sendWithProtocol(o).catch((e=>{i.error(e),delete this._callbacks[o.invocationId]})),this._launchStreams(n,s),i}_sendMessage(e){return this._resetKeepAliveInterval(),this.connection.send(e)}_sendWithProtocol(e){return this._messageBuffer?this._messageBuffer._send(e):this._sendMessage(this._protocol.writeMessage(e))}send(e,...t){const[n,r]=this._replaceStreamingParams(t),o=this._sendWithProtocol(this._createInvocation(e,t,!0,r));return this._launchStreams(n,o),o}invoke(e,...t){const[n,r]=this._replaceStreamingParams(t),o=this._createInvocation(e,t,!1,r);return new Promise(((e,t)=>{this._callbacks[o.invocationId]=(n,r)=>{r?t(r):n&&(n.type===qt.Completion?n.error?t(new Error(n.error)):e(n.result):t(new Error(`Unexpected message type: ${n.type}`)))};const r=this._sendWithProtocol(o).catch((e=>{t(e),delete this._callbacks[o.invocationId]}));this._launchStreams(n,r)}))}on(e,t){e&&t&&(e=e.toLowerCase(),this._methods[e]||(this._methods[e]=[]),-1===this._methods[e].indexOf(t)&&this._methods[e].push(t))}off(e,t){if(!e)return;e=e.toLowerCase();const n=this._methods[e];if(n)if(t){const r=n.indexOf(t);-1!==r&&(n.splice(r,1),0===n.length&&delete this._methods[e])}else delete this._methods[e]}onclose(e){e&&this._closedCallbacks.push(e)}onreconnecting(e){e&&this._reconnectingCallbacks.push(e)}onreconnected(e){e&&this._reconnectedCallbacks.push(e)}_processIncomingData(e){if(this._cleanupTimeout(),this._receivedHandshakeResponse||(e=this._processHandshakeResponse(e),this._receivedHandshakeResponse=!0),e){const t=this._protocol.parseMessages(e,this._logger);for(const e of t)if(!this._messageBuffer||this._messageBuffer._shouldProcessMessage(e))switch(e.type){case qt.Invocation:this._invokeClientMethod(e);break;case qt.StreamItem:case qt.Completion:{const t=this._callbacks[e.invocationId];if(t){e.type===qt.Completion&&delete this._callbacks[e.invocationId];try{t(e)}catch(e){this._logger.log(bt.Error,`Stream callback threw error: ${Mt(e)}`)}}break}case qt.Ping:break;case qt.Close:{this._logger.log(bt.Information,"Close message received from server.");const t=e.error?new Error("Server returned an error on close: "+e.error):void 0;!0===e.allowReconnect?this.connection.stop(t):this._stopPromise=this._stopInternal(t);break}case qt.Ack:this._messageBuffer&&this._messageBuffer._ack(e);break;case qt.Sequence:this._messageBuffer&&this._messageBuffer._resetSequence(e);break;default:this._logger.log(bt.Warning,`Invalid message type: ${e.type}.`)}}this._resetTimeoutPeriod()}_processHandshakeResponse(e){let t,n;try{[n,t]=this._handshakeProtocol.parseHandshakeResponse(e)}catch(e){const t="Error parsing handshake response: "+e;this._logger.log(bt.Error,t);const n=new Error(t);throw this._handshakeRejecter(n),n}if(t.error){const e="Server returned handshake error: "+t.error;this._logger.log(bt.Error,e);const n=new Error(e);throw this._handshakeRejecter(n),n}return this._logger.log(bt.Debug,"Server handshake complete."),this._handshakeResolver(),n}_resetKeepAliveInterval(){this.connection.features.inherentKeepAlive||(this._nextKeepAlive=(new Date).getTime()+this.keepAliveIntervalInMilliseconds,this._cleanupPingTimer())}_resetTimeoutPeriod(){if(!(this.connection.features&&this.connection.features.inherentKeepAlive||(this._timeoutHandle=setTimeout((()=>this.serverTimeout()),this.serverTimeoutInMilliseconds),void 0!==this._pingServerHandle))){let e=this._nextKeepAlive-(new Date).getTime();e<0&&(e=0),this._pingServerHandle=setTimeout((async()=>{if(this._connectionState===Jt.Connected)try{await this._sendMessage(this._cachedPingMessage)}catch{this._cleanupPingTimer()}}),e)}}serverTimeout(){this.connection.stop(new Error("Server timeout elapsed without receiving a message from the server."))}async _invokeClientMethod(e){const t=e.target.toLowerCase(),n=this._methods[t];if(!n)return this._logger.log(bt.Warning,`No client method with the name '${t}' found.`),void(e.invocationId&&(this._logger.log(bt.Warning,`No result given for '${t}' method and invocation ID '${e.invocationId}'.`),await this._sendWithProtocol(this._createCompletionMessage(e.invocationId,"Client didn't provide a result.",null))));const r=n.slice(),o=!!e.invocationId;let s,i,a;for(const n of r)try{const r=s;s=await n.apply(this,e.arguments),o&&s&&r&&(this._logger.log(bt.Error,`Multiple results provided for '${t}'. Sending error to server.`),a=this._createCompletionMessage(e.invocationId,"Client provided multiple results.",null)),i=void 0}catch(e){i=e,this._logger.log(bt.Error,`A callback for the method '${t}' threw error '${e}'.`)}a?await this._sendWithProtocol(a):o?(i?a=this._createCompletionMessage(e.invocationId,`${i}`,null):void 0!==s?a=this._createCompletionMessage(e.invocationId,null,s):(this._logger.log(bt.Warning,`No result given for '${t}' method and invocation ID '${e.invocationId}'.`),a=this._createCompletionMessage(e.invocationId,"Client didn't provide a result.",null)),await this._sendWithProtocol(a)):s&&this._logger.log(bt.Error,`Result given for '${t}' method but server is not expecting a result.`)}_connectionClosed(e){this._logger.log(bt.Debug,`HubConnection.connectionClosed(${e}) called while in state ${this._connectionState}.`),this._stopDuringStartError=this._stopDuringStartError||e||new Ot("The underlying connection was closed before the hub handshake could complete."),this._handshakeResolver&&this._handshakeResolver(),this._cancelCallbacksWithError(e||new Error("Invocation canceled due to the underlying connection being closed.")),this._cleanupTimeout(),this._cleanupPingTimer(),this._connectionState===Jt.Disconnecting?this._completeClose(e):this._connectionState===Jt.Connected&&this._reconnectPolicy?this._reconnect(e):this._connectionState===Jt.Connected&&this._completeClose(e)}_completeClose(e){if(this._connectionStarted){this._connectionState=Jt.Disconnected,this._connectionStarted=!1,this._messageBuffer&&(this._messageBuffer._dispose(null!=e?e:new Error("Connection closed.")),this._messageBuffer=void 0),It.isBrowser&&window.document.removeEventListener("freeze",this._freezeEventListener);try{this._closedCallbacks.forEach((t=>t.apply(this,[e])))}catch(t){this._logger.log(bt.Error,`An onclose callback called with error '${e}' threw error '${t}'.`)}}}async _reconnect(e){const t=Date.now();let n=0,r=void 0!==e?e:new Error("Attempting to reconnect due to a unknown error."),o=this._getNextRetryDelay(n++,0,r);if(null===o)return this._logger.log(bt.Debug,"Connection not reconnecting because the IRetryPolicy returned null on the first reconnect attempt."),void this._completeClose(e);if(this._connectionState=Jt.Reconnecting,e?this._logger.log(bt.Information,`Connection reconnecting because of error '${e}'.`):this._logger.log(bt.Information,"Connection reconnecting."),0!==this._reconnectingCallbacks.length){try{this._reconnectingCallbacks.forEach((t=>t.apply(this,[e])))}catch(t){this._logger.log(bt.Error,`An onreconnecting callback called with error '${e}' threw error '${t}'.`)}if(this._connectionState!==Jt.Reconnecting)return void this._logger.log(bt.Debug,"Connection left the reconnecting state in onreconnecting callback. Done reconnecting.")}for(;null!==o;){if(this._logger.log(bt.Information,`Reconnect attempt number ${n} will start in ${o} ms.`),await new Promise((e=>{this._reconnectDelayHandle=setTimeout(e,o)})),this._reconnectDelayHandle=void 0,this._connectionState!==Jt.Reconnecting)return void this._logger.log(bt.Debug,"Connection left the reconnecting state during reconnect delay. Done reconnecting.");try{if(await this._startInternal(),this._connectionState=Jt.Connected,this._logger.log(bt.Information,"HubConnection reconnected successfully."),0!==this._reconnectedCallbacks.length)try{this._reconnectedCallbacks.forEach((e=>e.apply(this,[this.connection.connectionId])))}catch(e){this._logger.log(bt.Error,`An onreconnected callback called with connectionId '${this.connection.connectionId}; threw error '${e}'.`)}return}catch(e){if(this._logger.log(bt.Information,`Reconnect attempt failed because of error '${e}'.`),this._connectionState!==Jt.Reconnecting)return this._logger.log(bt.Debug,`Connection moved to the '${this._connectionState}' from the reconnecting state during reconnect attempt. Done reconnecting.`),void(this._connectionState===Jt.Disconnecting&&this._completeClose());r=e instanceof Error?e:new Error(e.toString()),o=this._getNextRetryDelay(n++,Date.now()-t,r)}}this._logger.log(bt.Information,`Reconnect retries have been exhausted after ${Date.now()-t} ms and ${n} failed attempts. Connection disconnecting.`),this._completeClose()}_getNextRetryDelay(e,t,n){try{return this._reconnectPolicy.nextRetryDelayInMilliseconds({elapsedMilliseconds:t,previousRetryCount:e,retryReason:n})}catch(n){return this._logger.log(bt.Error,`IRetryPolicy.nextRetryDelayInMilliseconds(${e}, ${t}) threw error '${n}'.`),null}}_cancelCallbacksWithError(e){const t=this._callbacks;this._callbacks={},Object.keys(t).forEach((n=>{const r=t[n];try{r(null,e)}catch(t){this._logger.log(bt.Error,`Stream 'error' callback called with '${e}' threw error: ${Mt(t)}`)}}))}_cleanupPingTimer(){this._pingServerHandle&&(clearTimeout(this._pingServerHandle),this._pingServerHandle=void 0)}_cleanupTimeout(){this._timeoutHandle&&clearTimeout(this._timeoutHandle)}_createInvocation(e,t,n,r){if(n)return 0!==r.length?{arguments:t,streamIds:r,target:e,type:qt.Invocation}:{arguments:t,target:e,type:qt.Invocation};{const n=this._invocationId;return this._invocationId++,0!==r.length?{arguments:t,invocationId:n.toString(),streamIds:r,target:e,type:qt.Invocation}:{arguments:t,invocationId:n.toString(),target:e,type:qt.Invocation}}}_launchStreams(e,t){if(0!==e.length){t||(t=Promise.resolve());for(const n in e)e[n].subscribe({complete:()=>{t=t.then((()=>this._sendWithProtocol(this._createCompletionMessage(n))))},error:e=>{let r;r=e instanceof Error?e.message:e&&e.toString?e.toString():"Unknown error",t=t.then((()=>this._sendWithProtocol(this._createCompletionMessage(n,r))))},next:e=>{t=t.then((()=>this._sendWithProtocol(this._createStreamItemMessage(n,e))))}})}}_replaceStreamingParams(e){const t=[],n=[];for(let r=0;r0)&&(t=!1,this._accessToken=await this._accessTokenFactory()),this._setAuthorizationHeader(e);const n=await this._innerClient.send(e);return t&&401===n.statusCode&&this._accessTokenFactory?(this._accessToken=await this._accessTokenFactory(),this._setAuthorizationHeader(e),await this._innerClient.send(e)):n}_setAuthorizationHeader(e){e.headers||(e.headers={}),this._accessToken?e.headers[Zt.Authorization]=`Bearer ${this._accessToken}`:this._accessTokenFactory&&e.headers[Zt.Authorization]&&delete e.headers[Zt.Authorization]}getCookieString(e){return this._innerClient.getCookieString(e)}}class rn extends tn{constructor(e){super(),this._logger=e;const t={_fetchType:void 0,_jar:void 0};var r;r=t,"undefined"==typeof fetch&&(r._jar=new(n(628).CookieJar),"undefined"==typeof fetch?r._fetchType=n(200):r._fetchType=fetch,r._fetchType=n(203)(r._fetchType,r._jar),1)?(this._fetchType=t._fetchType,this._jar=t._jar):this._fetchType=fetch.bind(function(){if("undefined"!=typeof globalThis)return globalThis;if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if(void 0!==n.g)return n.g;throw new Error("could not find global")}()),this._abortControllerType=AbortController;const o={_abortControllerType:this._abortControllerType};(function(e){return"undefined"==typeof AbortController&&(e._abortControllerType=n(778),!0)})(o)&&(this._abortControllerType=o._abortControllerType)}async send(e){if(e.abortSignal&&e.abortSignal.aborted)throw new Ot;if(!e.method)throw new Error("No method defined.");if(!e.url)throw new Error("No url defined.");const t=new this._abortControllerType;let n;e.abortSignal&&(e.abortSignal.onabort=()=>{t.abort(),n=new Ot});let r,o=null;if(e.timeout){const r=e.timeout;o=setTimeout((()=>{t.abort(),this._logger.log(bt.Warning,"Timeout from HTTP request."),n=new $t}),r)}""===e.content&&(e.content=void 0),e.content&&(e.headers=e.headers||{},Tt(e.content)?e.headers["Content-Type"]="application/octet-stream":e.headers["Content-Type"]="text/plain;charset=UTF-8");try{r=await this._fetchType(e.url,{body:e.content,cache:"no-cache",credentials:!0===e.withCredentials?"include":"same-origin",headers:{"X-Requested-With":"XMLHttpRequest",...e.headers},method:e.method,mode:"cors",redirect:"follow",signal:t.signal})}catch(e){if(n)throw n;throw this._logger.log(bt.Warning,`Error from HTTP request. ${e}.`),e}finally{o&&clearTimeout(o),e.abortSignal&&(e.abortSignal.onabort=null)}if(!r.ok){const e=await on(r,"text");throw new Lt(e||r.statusText,r.status)}const s=on(r,e.responseType),i=await s;return new en(r.status,r.statusText,i)}getCookieString(e){return""}}function on(e,t){let n;switch(t){case"arraybuffer":n=e.arrayBuffer();break;case"text":default:n=e.text();break;case"blob":case"document":case"json":throw new Error(`${t} is not supported.`)}return n}class sn extends tn{constructor(e){super(),this._logger=e}send(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new Ot):e.method?e.url?new Promise(((t,n)=>{const r=new XMLHttpRequest;r.open(e.method,e.url,!0),r.withCredentials=void 0===e.withCredentials||e.withCredentials,r.setRequestHeader("X-Requested-With","XMLHttpRequest"),""===e.content&&(e.content=void 0),e.content&&(Tt(e.content)?r.setRequestHeader("Content-Type","application/octet-stream"):r.setRequestHeader("Content-Type","text/plain;charset=UTF-8"));const o=e.headers;o&&Object.keys(o).forEach((e=>{r.setRequestHeader(e,o[e])})),e.responseType&&(r.responseType=e.responseType),e.abortSignal&&(e.abortSignal.onabort=()=>{r.abort(),n(new Ot)}),e.timeout&&(r.timeout=e.timeout),r.onload=()=>{e.abortSignal&&(e.abortSignal.onabort=null),r.status>=200&&r.status<300?t(new en(r.status,r.statusText,r.response||r.responseText)):n(new Lt(r.response||r.responseText||r.statusText,r.status))},r.onerror=()=>{this._logger.log(bt.Warning,`Error from HTTP request. ${r.status}: ${r.statusText}.`),n(new Lt(r.statusText,r.status))},r.ontimeout=()=>{this._logger.log(bt.Warning,"Timeout from HTTP request."),n(new $t)},r.send(e.content)})):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}}class an extends tn{constructor(e){if(super(),"undefined"!=typeof fetch)this._httpClient=new rn(e);else{if("undefined"==typeof XMLHttpRequest)throw new Error("No usable HttpClient found.");this._httpClient=new sn(e)}}send(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new Ot):e.method?e.url?this._httpClient.send(e):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}getCookieString(e){return this._httpClient.getCookieString(e)}}var cn,ln;!function(e){e[e.None=0]="None",e[e.WebSockets=1]="WebSockets",e[e.ServerSentEvents=2]="ServerSentEvents",e[e.LongPolling=4]="LongPolling"}(cn||(cn={})),function(e){e[e.Text=1]="Text",e[e.Binary=2]="Binary"}(ln||(ln={}));class hn{constructor(){this._isAborted=!1,this.onabort=null}abort(){this._isAborted||(this._isAborted=!0,this.onabort&&this.onabort())}get signal(){return this}get aborted(){return this._isAborted}}class dn{get pollAborted(){return this._pollAbort.aborted}constructor(e,t,n){this._httpClient=e,this._logger=t,this._pollAbort=new hn,this._options=n,this._running=!1,this.onreceive=null,this.onclose=null}async connect(e,t){if(Ct.isRequired(e,"url"),Ct.isRequired(t,"transferFormat"),Ct.isIn(t,ln,"transferFormat"),this._url=e,this._logger.log(bt.Trace,"(LongPolling transport) Connecting."),t===ln.Binary&&"undefined"!=typeof XMLHttpRequest&&"string"!=typeof(new XMLHttpRequest).responseType)throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");const[n,r]=At(),o={[n]:r,...this._options.headers},s={abortSignal:this._pollAbort.signal,headers:o,timeout:1e5,withCredentials:this._options.withCredentials};t===ln.Binary&&(s.responseType="arraybuffer");const i=`${e}&_=${Date.now()}`;this._logger.log(bt.Trace,`(LongPolling transport) polling: ${i}.`);const a=await this._httpClient.get(i,s);200!==a.statusCode?(this._logger.log(bt.Error,`(LongPolling transport) Unexpected response code: ${a.statusCode}.`),this._closeError=new Lt(a.statusText||"",a.statusCode),this._running=!1):this._running=!0,this._receiving=this._poll(this._url,s)}async _poll(e,t){try{for(;this._running;)try{const n=`${e}&_=${Date.now()}`;this._logger.log(bt.Trace,`(LongPolling transport) polling: ${n}.`);const r=await this._httpClient.get(n,t);204===r.statusCode?(this._logger.log(bt.Information,"(LongPolling transport) Poll terminated by server."),this._running=!1):200!==r.statusCode?(this._logger.log(bt.Error,`(LongPolling transport) Unexpected response code: ${r.statusCode}.`),this._closeError=new Lt(r.statusText||"",r.statusCode),this._running=!1):r.content?(this._logger.log(bt.Trace,`(LongPolling transport) data received. ${kt(r.content,this._options.logMessageContent)}.`),this.onreceive&&this.onreceive(r.content)):this._logger.log(bt.Trace,"(LongPolling transport) Poll timed out, reissuing.")}catch(e){this._running?e instanceof $t?this._logger.log(bt.Trace,"(LongPolling transport) Poll timed out, reissuing."):(this._closeError=e,this._running=!1):this._logger.log(bt.Trace,`(LongPolling transport) Poll errored after shutdown: ${e.message}`)}}finally{this._logger.log(bt.Trace,"(LongPolling transport) Polling complete."),this.pollAborted||this._raiseOnClose()}}async send(e){return this._running?Dt(this._logger,"LongPolling",this._httpClient,this._url,e,this._options):Promise.reject(new Error("Cannot send until the transport is connected"))}async stop(){this._logger.log(bt.Trace,"(LongPolling transport) Stopping polling."),this._running=!1,this._pollAbort.abort();try{await this._receiving,this._logger.log(bt.Trace,`(LongPolling transport) sending DELETE request to ${this._url}.`);const e={},[t,n]=At();e[t]=n;const r={headers:{...e,...this._options.headers},timeout:this._options.timeout,withCredentials:this._options.withCredentials};let o;try{await this._httpClient.delete(this._url,r)}catch(e){o=e}o?o instanceof Lt&&(404===o.statusCode?this._logger.log(bt.Trace,"(LongPolling transport) A 404 response was returned from sending a DELETE request."):this._logger.log(bt.Trace,`(LongPolling transport) Error sending a DELETE request: ${o}`)):this._logger.log(bt.Trace,"(LongPolling transport) DELETE request accepted.")}finally{this._logger.log(bt.Trace,"(LongPolling transport) Stop finished."),this._raiseOnClose()}}_raiseOnClose(){if(this.onclose){let e="(LongPolling transport) Firing onclose event.";this._closeError&&(e+=" Error: "+this._closeError),this._logger.log(bt.Trace,e),this.onclose(this._closeError)}}}class un{constructor(e,t,n,r){this._httpClient=e,this._accessToken=t,this._logger=n,this._options=r,this.onreceive=null,this.onclose=null}async connect(e,t){return Ct.isRequired(e,"url"),Ct.isRequired(t,"transferFormat"),Ct.isIn(t,ln,"transferFormat"),this._logger.log(bt.Trace,"(SSE transport) Connecting."),this._url=e,this._accessToken&&(e+=(e.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(this._accessToken)}`),new Promise(((n,r)=>{let o,s=!1;if(t===ln.Text){if(It.isBrowser||It.isWebWorker)o=new this._options.EventSource(e,{withCredentials:this._options.withCredentials});else{const t=this._httpClient.getCookieString(e),n={};n.Cookie=t;const[r,s]=At();n[r]=s,o=new this._options.EventSource(e,{withCredentials:this._options.withCredentials,headers:{...n,...this._options.headers}})}try{o.onmessage=e=>{if(this.onreceive)try{this._logger.log(bt.Trace,`(SSE transport) data received. ${kt(e.data,this._options.logMessageContent)}.`),this.onreceive(e.data)}catch(e){return void this._close(e)}},o.onerror=e=>{s?this._close():r(new Error("EventSource failed to connect. The connection could not be found on the server, either the connection ID is not present on the server, or a proxy is refusing/buffering the connection. If you have multiple servers check that sticky sessions are enabled."))},o.onopen=()=>{this._logger.log(bt.Information,`SSE connected to ${this._url}`),this._eventSource=o,s=!0,n()}}catch(e){return void r(e)}}else r(new Error("The Server-Sent Events transport only supports the 'Text' transfer format"))}))}async send(e){return this._eventSource?Dt(this._logger,"SSE",this._httpClient,this._url,e,this._options):Promise.reject(new Error("Cannot send until the transport is connected"))}stop(){return this._close(),Promise.resolve()}_close(e){this._eventSource&&(this._eventSource.close(),this._eventSource=void 0,this.onclose&&this.onclose(e))}}class pn{constructor(e,t,n,r,o,s){this._logger=n,this._accessTokenFactory=t,this._logMessageContent=r,this._webSocketConstructor=o,this._httpClient=e,this.onreceive=null,this.onclose=null,this._headers=s}async connect(e,t){let n;return Ct.isRequired(e,"url"),Ct.isRequired(t,"transferFormat"),Ct.isIn(t,ln,"transferFormat"),this._logger.log(bt.Trace,"(WebSockets transport) Connecting."),this._accessTokenFactory&&(n=await this._accessTokenFactory()),new Promise(((r,o)=>{let s;e=e.replace(/^http/,"ws");const i=this._httpClient.getCookieString(e);let a=!1;if(It.isReactNative){const t={},[r,o]=At();t[r]=o,n&&(t[Zt.Authorization]=`Bearer ${n}`),i&&(t[Zt.Cookie]=i),s=new this._webSocketConstructor(e,void 0,{headers:{...t,...this._headers}})}else n&&(e+=(e.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(n)}`);s||(s=new this._webSocketConstructor(e)),t===ln.Binary&&(s.binaryType="arraybuffer"),s.onopen=t=>{this._logger.log(bt.Information,`WebSocket connected to ${e}.`),this._webSocket=s,a=!0,r()},s.onerror=e=>{let t=null;t="undefined"!=typeof ErrorEvent&&e instanceof ErrorEvent?e.error:"There was an error with the transport",this._logger.log(bt.Information,`(WebSockets transport) ${t}.`)},s.onmessage=e=>{if(this._logger.log(bt.Trace,`(WebSockets transport) data received. ${kt(e.data,this._logMessageContent)}.`),this.onreceive)try{this.onreceive(e.data)}catch(e){return void this._close(e)}},s.onclose=e=>{if(a)this._close(e);else{let t=null;t="undefined"!=typeof ErrorEvent&&e instanceof ErrorEvent?e.error:"WebSocket failed to connect. The connection could not be found on the server, either the endpoint may not be a SignalR endpoint, the connection ID is not present on the server, or there is a proxy blocking WebSockets. If you have multiple servers check that sticky sessions are enabled.",o(new Error(t))}}}))}send(e){return this._webSocket&&this._webSocket.readyState===this._webSocketConstructor.OPEN?(this._logger.log(bt.Trace,`(WebSockets transport) sending data. ${kt(e,this._logMessageContent)}.`),this._webSocket.send(e),Promise.resolve()):Promise.reject("WebSocket is not in the OPEN state")}stop(){return this._webSocket&&this._close(void 0),Promise.resolve()}_close(e){this._webSocket&&(this._webSocket.onclose=()=>{},this._webSocket.onmessage=()=>{},this._webSocket.onerror=()=>{},this._webSocket.close(),this._webSocket=void 0),this._logger.log(bt.Trace,"(WebSockets transport) socket closed."),this.onclose&&(!this._isCloseEvent(e)||!1!==e.wasClean&&1e3===e.code?e instanceof Error?this.onclose(e):this.onclose():this.onclose(new Error(`WebSocket closed with status code: ${e.code} (${e.reason||"no reason given"}).`)))}_isCloseEvent(e){return e&&"boolean"==typeof e.wasClean&&"number"==typeof e.code}}class fn{constructor(e,t={}){if(this._stopPromiseResolver=()=>{},this.features={},this._negotiateVersion=1,Ct.isRequired(e,"url"),this._logger=function(e){return void 0===e?new xt(bt.Information):null===e?St.instance:void 0!==e.log?e:new xt(e)}(t.logger),this.baseUrl=this._resolveUrl(e),(t=t||{}).logMessageContent=void 0!==t.logMessageContent&&t.logMessageContent,"boolean"!=typeof t.withCredentials&&void 0!==t.withCredentials)throw new Error("withCredentials option was not a 'boolean' or 'undefined' value");t.withCredentials=void 0===t.withCredentials||t.withCredentials,t.timeout=void 0===t.timeout?1e5:t.timeout,"undefined"==typeof WebSocket||t.WebSocket||(t.WebSocket=WebSocket),"undefined"==typeof EventSource||t.EventSource||(t.EventSource=EventSource),this._httpClient=new nn(t.httpClient||new an(this._logger),t.accessTokenFactory),this._connectionState="Disconnected",this._connectionStarted=!1,this._options=t,this.onreceive=null,this.onclose=null}async start(e){if(e=e||ln.Binary,Ct.isIn(e,ln,"transferFormat"),this._logger.log(bt.Debug,`Starting connection with transfer format '${ln[e]}'.`),"Disconnected"!==this._connectionState)return Promise.reject(new Error("Cannot start an HttpConnection that is not in the 'Disconnected' state."));if(this._connectionState="Connecting",this._startInternalPromise=this._startInternal(e),await this._startInternalPromise,"Disconnecting"===this._connectionState){const e="Failed to start the HttpConnection before stop() was called.";return this._logger.log(bt.Error,e),await this._stopPromise,Promise.reject(new Ot(e))}if("Connected"!==this._connectionState){const e="HttpConnection.startInternal completed gracefully but didn't enter the connection into the connected state!";return this._logger.log(bt.Error,e),Promise.reject(new Ot(e))}this._connectionStarted=!0}send(e){return"Connected"!==this._connectionState?Promise.reject(new Error("Cannot send data if the connection is not in the 'Connected' State.")):(this._sendQueue||(this._sendQueue=new gn(this.transport)),this._sendQueue.send(e))}async stop(e){return"Disconnected"===this._connectionState?(this._logger.log(bt.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnected state.`),Promise.resolve()):"Disconnecting"===this._connectionState?(this._logger.log(bt.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnecting state.`),this._stopPromise):(this._connectionState="Disconnecting",this._stopPromise=new Promise((e=>{this._stopPromiseResolver=e})),await this._stopInternal(e),void await this._stopPromise)}async _stopInternal(e){this._stopError=e;try{await this._startInternalPromise}catch(e){}if(this.transport){try{await this.transport.stop()}catch(e){this._logger.log(bt.Error,`HttpConnection.transport.stop() threw error '${e}'.`),this._stopConnection()}this.transport=void 0}else this._logger.log(bt.Debug,"HttpConnection.transport is undefined in HttpConnection.stop() because start() failed.")}async _startInternal(e){let t=this.baseUrl;this._accessTokenFactory=this._options.accessTokenFactory,this._httpClient._accessTokenFactory=this._accessTokenFactory;try{if(this._options.skipNegotiation){if(this._options.transport!==cn.WebSockets)throw new Error("Negotiation can only be skipped when using the WebSocket transport directly.");this.transport=this._constructTransport(cn.WebSockets),await this._startTransport(t,e)}else{let n=null,r=0;do{if(n=await this._getNegotiationResponse(t),"Disconnecting"===this._connectionState||"Disconnected"===this._connectionState)throw new Ot("The connection was stopped during negotiation.");if(n.error)throw new Error(n.error);if(n.ProtocolVersion)throw new Error("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.");if(n.url&&(t=n.url),n.accessToken){const e=n.accessToken;this._accessTokenFactory=()=>e,this._httpClient._accessToken=e,this._httpClient._accessTokenFactory=void 0}r++}while(n.url&&r<100);if(100===r&&n.url)throw new Error("Negotiate redirection limit exceeded.");await this._createTransport(t,this._options.transport,n,e)}this.transport instanceof dn&&(this.features.inherentKeepAlive=!0),"Connecting"===this._connectionState&&(this._logger.log(bt.Debug,"The HttpConnection connected successfully."),this._connectionState="Connected")}catch(e){return this._logger.log(bt.Error,"Failed to start the connection: "+e),this._connectionState="Disconnected",this.transport=void 0,this._stopPromiseResolver(),Promise.reject(e)}}async _getNegotiationResponse(e){const t={},[n,r]=At();t[n]=r;const o=this._resolveNegotiateUrl(e);this._logger.log(bt.Debug,`Sending negotiation request: ${o}.`);try{const e=await this._httpClient.post(o,{content:"",headers:{...t,...this._options.headers},timeout:this._options.timeout,withCredentials:this._options.withCredentials});if(200!==e.statusCode)return Promise.reject(new Error(`Unexpected status code returned from negotiate '${e.statusCode}'`));const n=JSON.parse(e.content);return(!n.negotiateVersion||n.negotiateVersion<1)&&(n.connectionToken=n.connectionId),n.useStatefulReconnect&&!0!==this._options._useStatefulReconnect?Promise.reject(new Wt("Client didn't negotiate Stateful Reconnect but the server did.")):n}catch(e){let t="Failed to complete negotiation with the server: "+e;return e instanceof Lt&&404===e.statusCode&&(t+=" Either this is not a SignalR endpoint or there is a proxy blocking the connection."),this._logger.log(bt.Error,t),Promise.reject(new Wt(t))}}_createConnectUrl(e,t){return t?e+(-1===e.indexOf("?")?"?":"&")+`id=${t}`:e}async _createTransport(e,t,n,r){let o=this._createConnectUrl(e,n.connectionToken);if(this._isITransport(t))return this._logger.log(bt.Debug,"Connection was provided an instance of ITransport, using that directly."),this.transport=t,await this._startTransport(o,r),void(this.connectionId=n.connectionId);const s=[],i=n.availableTransports||[];let a=n;for(const n of i){const i=this._resolveTransportOrError(n,t,r,!0===(null==a?void 0:a.useStatefulReconnect));if(i instanceof Error)s.push(`${n.transport} failed:`),s.push(i);else if(this._isITransport(i)){if(this.transport=i,!a){try{a=await this._getNegotiationResponse(e)}catch(e){return Promise.reject(e)}o=this._createConnectUrl(e,a.connectionToken)}try{return await this._startTransport(o,r),void(this.connectionId=a.connectionId)}catch(e){if(this._logger.log(bt.Error,`Failed to start the transport '${n.transport}': ${e}`),a=void 0,s.push(new jt(`${n.transport} failed: ${e}`,cn[n.transport])),"Connecting"!==this._connectionState){const e="Failed to select transport before stop() was called.";return this._logger.log(bt.Debug,e),Promise.reject(new Ot(e))}}}}return s.length>0?Promise.reject(new zt(`Unable to connect to the server with any of the available transports. ${s.join(" ")}`,s)):Promise.reject(new Error("None of the transports supported by the client are supported by the server."))}_constructTransport(e){switch(e){case cn.WebSockets:if(!this._options.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new pn(this._httpClient,this._accessTokenFactory,this._logger,this._options.logMessageContent,this._options.WebSocket,this._options.headers||{});case cn.ServerSentEvents:if(!this._options.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new un(this._httpClient,this._httpClient._accessToken,this._logger,this._options);case cn.LongPolling:return new dn(this._httpClient,this._logger,this._options);default:throw new Error(`Unknown transport: ${e}.`)}}_startTransport(e,t){return this.transport.onreceive=this.onreceive,this.features.reconnect?this.transport.onclose=async n=>{let r=!1;if(this.features.reconnect){try{this.features.disconnected(),await this.transport.connect(e,t),await this.features.resend()}catch{r=!0}r&&this._stopConnection(n)}else this._stopConnection(n)}:this.transport.onclose=e=>this._stopConnection(e),this.transport.connect(e,t)}_resolveTransportOrError(e,t,n,r){const o=cn[e.transport];if(null==o)return this._logger.log(bt.Debug,`Skipping transport '${e.transport}' because it is not supported by this client.`),new Error(`Skipping transport '${e.transport}' because it is not supported by this client.`);if(!function(e,t){return!e||0!=(t&e)}(t,o))return this._logger.log(bt.Debug,`Skipping transport '${cn[o]}' because it was disabled by the client.`),new Ht(`'${cn[o]}' is disabled by the client.`,o);if(!(e.transferFormats.map((e=>ln[e])).indexOf(n)>=0))return this._logger.log(bt.Debug,`Skipping transport '${cn[o]}' because it does not support the requested transfer format '${ln[n]}'.`),new Error(`'${cn[o]}' does not support ${ln[n]}.`);if(o===cn.WebSockets&&!this._options.WebSocket||o===cn.ServerSentEvents&&!this._options.EventSource)return this._logger.log(bt.Debug,`Skipping transport '${cn[o]}' because it is not supported in your environment.'`),new Ft(`'${cn[o]}' is not supported in your environment.`,o);this._logger.log(bt.Debug,`Selecting transport '${cn[o]}'.`);try{return this.features.reconnect=o===cn.WebSockets?r:void 0,this._constructTransport(o)}catch(e){return e}}_isITransport(e){return e&&"object"==typeof e&&"connect"in e}_stopConnection(e){if(this._logger.log(bt.Debug,`HttpConnection.stopConnection(${e}) called while in state ${this._connectionState}.`),this.transport=void 0,e=this._stopError||e,this._stopError=void 0,"Disconnected"!==this._connectionState){if("Connecting"===this._connectionState)throw this._logger.log(bt.Warning,`Call to HttpConnection.stopConnection(${e}) was ignored because the connection is still in the connecting state.`),new Error(`HttpConnection.stopConnection(${e}) was called while the connection is still in the connecting state.`);if("Disconnecting"===this._connectionState&&this._stopPromiseResolver(),e?this._logger.log(bt.Error,`Connection disconnected with error '${e}'.`):this._logger.log(bt.Information,"Connection disconnected."),this._sendQueue&&(this._sendQueue.stop().catch((e=>{this._logger.log(bt.Error,`TransportSendQueue.stop() threw error '${e}'.`)})),this._sendQueue=void 0),this.connectionId=void 0,this._connectionState="Disconnected",this._connectionStarted){this._connectionStarted=!1;try{this.onclose&&this.onclose(e)}catch(t){this._logger.log(bt.Error,`HttpConnection.onclose(${e}) threw error '${t}'.`)}}}else this._logger.log(bt.Debug,`Call to HttpConnection.stopConnection(${e}) was ignored because the connection is already in the disconnected state.`)}_resolveUrl(e){if(0===e.lastIndexOf("https://",0)||0===e.lastIndexOf("http://",0))return e;if(!It.isBrowser)throw new Error(`Cannot resolve '${e}'.`);const t=window.document.createElement("a");return t.href=e,this._logger.log(bt.Information,`Normalizing '${e}' to '${t.href}'.`),t.href}_resolveNegotiateUrl(e){const t=new URL(e);t.pathname.endsWith("/")?t.pathname+="negotiate":t.pathname+="/negotiate";const n=new URLSearchParams(t.searchParams);return n.has("negotiateVersion")||n.append("negotiateVersion",this._negotiateVersion.toString()),n.has("useStatefulReconnect")?"true"===n.get("useStatefulReconnect")&&(this._options._useStatefulReconnect=!0):!0===this._options._useStatefulReconnect&&n.append("useStatefulReconnect","true"),t.search=n.toString(),t.toString()}}class gn{constructor(e){this._transport=e,this._buffer=[],this._executing=!0,this._sendBufferedData=new mn,this._transportResult=new mn,this._sendLoopPromise=this._sendLoop()}send(e){return this._bufferData(e),this._transportResult||(this._transportResult=new mn),this._transportResult.promise}stop(){return this._executing=!1,this._sendBufferedData.resolve(),this._sendLoopPromise}_bufferData(e){if(this._buffer.length&&typeof this._buffer[0]!=typeof e)throw new Error(`Expected data to be of type ${typeof this._buffer} but was of type ${typeof e}`);this._buffer.push(e),this._sendBufferedData.resolve()}async _sendLoop(){for(;;){if(await this._sendBufferedData.promise,!this._executing){this._transportResult&&this._transportResult.reject("Connection stopped.");break}this._sendBufferedData=new mn;const e=this._transportResult;this._transportResult=void 0;const t="string"==typeof this._buffer[0]?this._buffer.join(""):gn._concatBuffers(this._buffer);this._buffer.length=0;try{await this._transport.send(t),e.resolve()}catch(t){e.reject(t)}}}static _concatBuffers(e){const t=e.map((e=>e.byteLength)).reduce(((e,t)=>e+t)),n=new Uint8Array(t);let r=0;for(const t of e)n.set(new Uint8Array(t),r),r+=t.byteLength;return n.buffer}}class mn{constructor(){this.promise=new Promise(((e,t)=>[this._resolver,this._rejecter]=[e,t]))}resolve(){this._resolver()}reject(e){this._rejecter(e)}}class vn{constructor(){this.name="json",this.version=2,this.transferFormat=ln.Text}parseMessages(e,t){if("string"!=typeof e)throw new Error("Invalid input for JSON hub protocol. Expected a string.");if(!e)return[];null===t&&(t=St.instance);const n=_t.parse(e),r=[];for(const e of n){const n=JSON.parse(e);if("number"!=typeof n.type)throw new Error("Invalid payload.");switch(n.type){case qt.Invocation:this._isInvocationMessage(n);break;case qt.StreamItem:this._isStreamItemMessage(n);break;case qt.Completion:this._isCompletionMessage(n);break;case qt.Ping:case qt.Close:break;case qt.Ack:this._isAckMessage(n);break;case qt.Sequence:this._isSequenceMessage(n);break;default:t.log(bt.Information,"Unknown message type '"+n.type+"' ignored.");continue}r.push(n)}return r}writeMessage(e){return _t.write(JSON.stringify(e))}_isInvocationMessage(e){this._assertNotEmptyString(e.target,"Invalid payload for Invocation message."),void 0!==e.invocationId&&this._assertNotEmptyString(e.invocationId,"Invalid payload for Invocation message.")}_isStreamItemMessage(e){if(this._assertNotEmptyString(e.invocationId,"Invalid payload for StreamItem message."),void 0===e.item)throw new Error("Invalid payload for StreamItem message.")}_isCompletionMessage(e){if(e.result&&e.error)throw new Error("Invalid payload for Completion message.");!e.result&&e.error&&this._assertNotEmptyString(e.error,"Invalid payload for Completion message."),this._assertNotEmptyString(e.invocationId,"Invalid payload for Completion message.")}_isAckMessage(e){if("number"!=typeof e.sequenceId)throw new Error("Invalid SequenceId for Ack message.")}_isSequenceMessage(e){if("number"!=typeof e.sequenceId)throw new Error("Invalid SequenceId for Sequence message.")}_assertNotEmptyString(e,t){if("string"!=typeof e||""===e)throw new Error(t)}}const yn={trace:bt.Trace,debug:bt.Debug,info:bt.Information,information:bt.Information,warn:bt.Warning,warning:bt.Warning,error:bt.Error,critical:bt.Critical,none:bt.None};class wn{configureLogging(e){if(Ct.isRequired(e,"logging"),function(e){return void 0!==e.log}(e))this.logger=e;else if("string"==typeof e){const t=function(e){const t=yn[e.toLowerCase()];if(void 0!==t)return t;throw new Error(`Unknown log level: ${e}`)}(e);this.logger=new xt(t)}else this.logger=new xt(e);return this}withUrl(e,t){return Ct.isRequired(e,"url"),Ct.isNotEmpty(e,"url"),this.url=e,this.httpConnectionOptions="object"==typeof t?{...this.httpConnectionOptions,...t}:{...this.httpConnectionOptions,transport:t},this}withHubProtocol(e){return Ct.isRequired(e,"protocol"),this.protocol=e,this}withAutomaticReconnect(e){if(this.reconnectPolicy)throw new Error("A reconnectPolicy has already been set.");return e?Array.isArray(e)?this.reconnectPolicy=new Qt(e):this.reconnectPolicy=e:this.reconnectPolicy=new Qt,this}withServerTimeout(e){return Ct.isRequired(e,"milliseconds"),this._serverTimeoutInMilliseconds=e,this}withKeepAliveInterval(e){return Ct.isRequired(e,"milliseconds"),this._keepAliveIntervalInMilliseconds=e,this}withStatefulReconnect(e){return void 0===this.httpConnectionOptions&&(this.httpConnectionOptions={}),this.httpConnectionOptions._useStatefulReconnect=!0,this._statefulReconnectBufferSize=null==e?void 0:e.bufferSize,this}build(){const e=this.httpConnectionOptions||{};if(void 0===e.logger&&(e.logger=this.logger),!this.url)throw new Error("The 'HubConnectionBuilder.withUrl' method must be called before building the connection.");const t=new fn(this.url,e);return Yt.create(t,this.logger||St.instance,this.protocol||new vn,this.reconnectPolicy,this._serverTimeoutInMilliseconds,this._keepAliveIntervalInMilliseconds,this._statefulReconnectBufferSize)}}var _n;!function(e){e[e.Default=0]="Default",e[e.Server=1]="Server",e[e.WebAssembly=2]="WebAssembly",e[e.WebView=3]="WebView"}(_n||(_n={}));var bn,Sn,En,Cn=4294967295;function In(e,t,n){var r=Math.floor(n/4294967296),o=n;e.setUint32(t,r),e.setUint32(t+4,o)}function kn(e,t){return 4294967296*e.getInt32(t)+e.getUint32(t+4)}var Tn=("undefined"==typeof process||"never"!==(null===(bn=null===process||void 0===process?void 0:process.env)||void 0===bn?void 0:bn.TEXT_ENCODING))&&"undefined"!=typeof TextEncoder&&"undefined"!=typeof TextDecoder;function Dn(e){for(var t=e.length,n=0,r=0;r=55296&&o<=56319&&r65535&&(h-=65536,s.push(h>>>10&1023|55296),h=56320|1023&h),s.push(h)}else s.push(a);s.length>=4096&&(i+=String.fromCharCode.apply(String,s),s.length=0)}return s.length>0&&(i+=String.fromCharCode.apply(String,s)),i}var Nn,Un=Tn?new TextDecoder:null,Mn=Tn?"undefined"!=typeof process&&"force"!==(null===(En=null===process||void 0===process?void 0:process.env)||void 0===En?void 0:En.TEXT_DECODER)?200:0:Cn,Bn=function(e,t){this.type=e,this.data=t},Ln=(Nn=function(e,t){return Nn=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},Nn(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}Nn(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),$n=function(e){function t(n){var r=e.call(this,n)||this,o=Object.create(t.prototype);return Object.setPrototypeOf(r,o),Object.defineProperty(r,"name",{configurable:!0,enumerable:!1,value:t.name}),r}return Ln(t,e),t}(Error),On={type:-1,encode:function(e){var t,n,r,o;return e instanceof Date?function(e){var t,n=e.sec,r=e.nsec;if(n>=0&&r>=0&&n<=17179869183){if(0===r&&n<=4294967295){var o=new Uint8Array(4);return(t=new DataView(o.buffer)).setUint32(0,n),o}var s=n/4294967296,i=4294967295&n;return o=new Uint8Array(8),(t=new DataView(o.buffer)).setUint32(0,r<<2|3&s),t.setUint32(4,i),o}return o=new Uint8Array(12),(t=new DataView(o.buffer)).setUint32(0,r),In(t,4,n),o}((r=1e6*((t=e.getTime())-1e3*(n=Math.floor(t/1e3))),{sec:n+(o=Math.floor(r/1e9)),nsec:r-1e9*o})):null},decode:function(e){var t=function(e){var t=new DataView(e.buffer,e.byteOffset,e.byteLength);switch(e.byteLength){case 4:return{sec:t.getUint32(0),nsec:0};case 8:var n=t.getUint32(0);return{sec:4294967296*(3&n)+t.getUint32(4),nsec:n>>>2};case 12:return{sec:kn(t,4),nsec:t.getUint32(0)};default:throw new $n("Unrecognized data size for timestamp (expected 4, 8, or 12): ".concat(e.length))}}(e);return new Date(1e3*t.sec+t.nsec/1e6)}},Fn=function(){function e(){this.builtInEncoders=[],this.builtInDecoders=[],this.encoders=[],this.decoders=[],this.register(On)}return e.prototype.register=function(e){var t=e.type,n=e.encode,r=e.decode;if(t>=0)this.encoders[t]=n,this.decoders[t]=r;else{var o=1+t;this.builtInEncoders[o]=n,this.builtInDecoders[o]=r}},e.prototype.tryToEncode=function(e,t){for(var n=0;nthis.maxDepth)throw new Error("Too deep objects in depth ".concat(t));null==e?this.encodeNil():"boolean"==typeof e?this.encodeBoolean(e):"number"==typeof e?this.encodeNumber(e):"string"==typeof e?this.encodeString(e):this.encodeObject(e,t)},e.prototype.ensureBufferSizeToWrite=function(e){var t=this.pos+e;this.view.byteLength=0?e<128?this.writeU8(e):e<256?(this.writeU8(204),this.writeU8(e)):e<65536?(this.writeU8(205),this.writeU16(e)):e<4294967296?(this.writeU8(206),this.writeU32(e)):(this.writeU8(207),this.writeU64(e)):e>=-32?this.writeU8(224|e+32):e>=-128?(this.writeU8(208),this.writeI8(e)):e>=-32768?(this.writeU8(209),this.writeI16(e)):e>=-2147483648?(this.writeU8(210),this.writeI32(e)):(this.writeU8(211),this.writeI64(e)):this.forceFloat32?(this.writeU8(202),this.writeF32(e)):(this.writeU8(203),this.writeF64(e))},e.prototype.writeStringHeader=function(e){if(e<32)this.writeU8(160+e);else if(e<256)this.writeU8(217),this.writeU8(e);else if(e<65536)this.writeU8(218),this.writeU16(e);else{if(!(e<4294967296))throw new Error("Too long string: ".concat(e," bytes in UTF-8"));this.writeU8(219),this.writeU32(e)}},e.prototype.encodeString=function(e){if(e.length>xn){var t=Dn(e);this.ensureBufferSizeToWrite(5+t),this.writeStringHeader(t),An(e,this.bytes,this.pos),this.pos+=t}else t=Dn(e),this.ensureBufferSizeToWrite(5+t),this.writeStringHeader(t),function(e,t,n){for(var r=e.length,o=n,s=0;s>6&31|192;else{if(i>=55296&&i<=56319&&s>12&15|224,t[o++]=i>>6&63|128):(t[o++]=i>>18&7|240,t[o++]=i>>12&63|128,t[o++]=i>>6&63|128)}t[o++]=63&i|128}else t[o++]=i}}(e,this.bytes,this.pos),this.pos+=t},e.prototype.encodeObject=function(e,t){var n=this.extensionCodec.tryToEncode(e,this.context);if(null!=n)this.encodeExtension(n);else if(Array.isArray(e))this.encodeArray(e,t);else if(ArrayBuffer.isView(e))this.encodeBinary(e);else{if("object"!=typeof e)throw new Error("Unrecognized object: ".concat(Object.prototype.toString.apply(e)));this.encodeMap(e,t)}},e.prototype.encodeBinary=function(e){var t=e.byteLength;if(t<256)this.writeU8(196),this.writeU8(t);else if(t<65536)this.writeU8(197),this.writeU16(t);else{if(!(t<4294967296))throw new Error("Too large binary: ".concat(t));this.writeU8(198),this.writeU32(t)}var n=Hn(e);this.writeU8a(n)},e.prototype.encodeArray=function(e,t){var n=e.length;if(n<16)this.writeU8(144+n);else if(n<65536)this.writeU8(220),this.writeU16(n);else{if(!(n<4294967296))throw new Error("Too large array: ".concat(n));this.writeU8(221),this.writeU32(n)}for(var r=0,o=e;r0&&e<=this.maxKeyLength},e.prototype.find=function(e,t,n){e:for(var r=0,o=this.caches[n-1];r=this.maxLengthPerKey?n[Math.random()*n.length|0]=r:n.push(r)},e.prototype.decode=function(e,t,n){var r=this.find(e,t,n);if(null!=r)return this.hit++,r;this.miss++;var o=Pn(e,t,n),s=Uint8Array.prototype.slice.call(e,t,t+n);return this.store(s,o),o},e}(),qn=function(e,t){var n,r,o,s,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return i.label++,{value:s[1],done:!1};case 5:i.label++,r=s[1],s=[0];continue;case 7:s=i.ops.pop(),i.trys.pop();continue;default:if(!((o=(o=i.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]=e},e.prototype.createExtraByteError=function(e){var t=this.view,n=this.pos;return new RangeError("Extra ".concat(t.byteLength-n," of ").concat(t.byteLength," byte(s) found at buffer[").concat(e,"]"))},e.prototype.decode=function(e){this.reinitializeState(),this.setBuffer(e);var t=this.doDecodeSync();if(this.hasRemaining(1))throw this.createExtraByteError(this.pos);return t},e.prototype.decodeMulti=function(e){return qn(this,(function(t){switch(t.label){case 0:this.reinitializeState(),this.setBuffer(e),t.label=1;case 1:return this.hasRemaining(1)?[4,this.doDecodeSync()]:[3,3];case 2:return t.sent(),[3,1];case 3:return[2]}}))},e.prototype.decodeAsync=function(e){var t,n,r,o,s,i,a;return s=this,void 0,a=function(){var s,i,a,c,l,h,d,u;return qn(this,(function(p){switch(p.label){case 0:s=!1,p.label=1;case 1:p.trys.push([1,6,7,12]),t=Jn(e),p.label=2;case 2:return[4,t.next()];case 3:if((n=p.sent()).done)return[3,5];if(a=n.value,s)throw this.createExtraByteError(this.totalPos);this.appendBuffer(a);try{i=this.doDecodeSync(),s=!0}catch(e){if(!(e instanceof Yn))throw e}this.totalPos+=this.pos,p.label=4;case 4:return[3,2];case 5:return[3,12];case 6:return c=p.sent(),r={error:c},[3,12];case 7:return p.trys.push([7,,10,11]),n&&!n.done&&(o=t.return)?[4,o.call(t)]:[3,9];case 8:p.sent(),p.label=9;case 9:return[3,11];case 10:if(r)throw r.error;return[7];case 11:return[7];case 12:if(s){if(this.hasRemaining(1))throw this.createExtraByteError(this.totalPos);return[2,i]}throw h=(l=this).headByte,d=l.pos,u=l.totalPos,new RangeError("Insufficient data in parsing ".concat(Wn(h)," at ").concat(u," (").concat(d," in the current buffer)"))}}))},new((i=void 0)||(i=Promise))((function(e,t){function n(e){try{o(a.next(e))}catch(e){t(e)}}function r(e){try{o(a.throw(e))}catch(e){t(e)}}function o(t){var o;t.done?e(t.value):(o=t.value,o instanceof i?o:new i((function(e){e(o)}))).then(n,r)}o((a=a.apply(s,[])).next())}))},e.prototype.decodeArrayStream=function(e){return this.decodeMultiAsync(e,!0)},e.prototype.decodeStream=function(e){return this.decodeMultiAsync(e,!1)},e.prototype.decodeMultiAsync=function(e,t){return function(n,r,o){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var s,i=function(){var n,r,o,s,i,a,c,l,h;return qn(this,(function(d){switch(d.label){case 0:n=t,r=-1,d.label=1;case 1:d.trys.push([1,13,14,19]),o=Jn(e),d.label=2;case 2:return[4,Kn(o.next())];case 3:if((s=d.sent()).done)return[3,12];if(i=s.value,t&&0===r)throw this.createExtraByteError(this.totalPos);this.appendBuffer(i),n&&(r=this.readArraySize(),n=!1,this.complete()),d.label=4;case 4:d.trys.push([4,9,,10]),d.label=5;case 5:return[4,Kn(this.doDecodeSync())];case 6:return[4,d.sent()];case 7:return d.sent(),0==--r?[3,8]:[3,5];case 8:return[3,10];case 9:if(!((a=d.sent())instanceof Yn))throw a;return[3,10];case 10:this.totalPos+=this.pos,d.label=11;case 11:return[3,2];case 12:return[3,19];case 13:return c=d.sent(),l={error:c},[3,19];case 14:return d.trys.push([14,,17,18]),s&&!s.done&&(h=o.return)?[4,Kn(h.call(o))]:[3,16];case 15:d.sent(),d.label=16;case 16:return[3,18];case 17:if(l)throw l.error;return[7];case 18:return[7];case 19:return[2]}}))}.apply(n,r||[]),a=[];return s={},c("next"),c("throw"),c("return"),s[Symbol.asyncIterator]=function(){return this},s;function c(e){i[e]&&(s[e]=function(t){return new Promise((function(n,r){a.push([e,t,n,r])>1||l(e,t)}))})}function l(e,t){try{(n=i[e](t)).value instanceof Kn?Promise.resolve(n.value.v).then(h,d):u(a[0][2],n)}catch(e){u(a[0][3],e)}var n}function h(e){l("next",e)}function d(e){l("throw",e)}function u(e,t){e(t),a.shift(),a.length&&l(a[0][0],a[0][1])}}(this,arguments)},e.prototype.doDecodeSync=function(){e:for(;;){var e=this.readHeadByte(),t=void 0;if(e>=224)t=e-256;else if(e<192)if(e<128)t=e;else if(e<144){if(0!=(r=e-128)){this.pushMapState(r),this.complete();continue e}t={}}else if(e<160){if(0!=(r=e-144)){this.pushArrayState(r),this.complete();continue e}t=[]}else{var n=e-160;t=this.decodeUtf8String(n,0)}else if(192===e)t=null;else if(194===e)t=!1;else if(195===e)t=!0;else if(202===e)t=this.readF32();else if(203===e)t=this.readF64();else if(204===e)t=this.readU8();else if(205===e)t=this.readU16();else if(206===e)t=this.readU32();else if(207===e)t=this.readU64();else if(208===e)t=this.readI8();else if(209===e)t=this.readI16();else if(210===e)t=this.readI32();else if(211===e)t=this.readI64();else if(217===e)n=this.lookU8(),t=this.decodeUtf8String(n,1);else if(218===e)n=this.lookU16(),t=this.decodeUtf8String(n,2);else if(219===e)n=this.lookU32(),t=this.decodeUtf8String(n,4);else if(220===e){if(0!==(r=this.readU16())){this.pushArrayState(r),this.complete();continue e}t=[]}else if(221===e){if(0!==(r=this.readU32())){this.pushArrayState(r),this.complete();continue e}t=[]}else if(222===e){if(0!==(r=this.readU16())){this.pushMapState(r),this.complete();continue e}t={}}else if(223===e){if(0!==(r=this.readU32())){this.pushMapState(r),this.complete();continue e}t={}}else if(196===e){var r=this.lookU8();t=this.decodeBinary(r,1)}else if(197===e)r=this.lookU16(),t=this.decodeBinary(r,2);else if(198===e)r=this.lookU32(),t=this.decodeBinary(r,4);else if(212===e)t=this.decodeExtension(1,0);else if(213===e)t=this.decodeExtension(2,0);else if(214===e)t=this.decodeExtension(4,0);else if(215===e)t=this.decodeExtension(8,0);else if(216===e)t=this.decodeExtension(16,0);else if(199===e)r=this.lookU8(),t=this.decodeExtension(r,1);else if(200===e)r=this.lookU16(),t=this.decodeExtension(r,2);else{if(201!==e)throw new $n("Unrecognized type byte: ".concat(Wn(e)));r=this.lookU32(),t=this.decodeExtension(r,4)}this.complete();for(var o=this.stack;o.length>0;){var s=o[o.length-1];if(0===s.type){if(s.array[s.position]=t,s.position++,s.position!==s.size)continue e;o.pop(),t=s.array}else{if(1===s.type){if("string"!=(i=typeof t)&&"number"!==i)throw new $n("The type of key must be string or number but "+typeof t);if("__proto__"===t)throw new $n("The key __proto__ is not allowed");s.key=t,s.type=2;continue e}if(s.map[s.key]=t,s.readCount++,s.readCount!==s.size){s.key=null,s.type=1;continue e}o.pop(),t=s.map}}return t}var i},e.prototype.readHeadByte=function(){return-1===this.headByte&&(this.headByte=this.readU8()),this.headByte},e.prototype.complete=function(){this.headByte=-1},e.prototype.readArraySize=function(){var e=this.readHeadByte();switch(e){case 220:return this.readU16();case 221:return this.readU32();default:if(e<160)return e-144;throw new $n("Unrecognized array type byte: ".concat(Wn(e)))}},e.prototype.pushMapState=function(e){if(e>this.maxMapLength)throw new $n("Max length exceeded: map length (".concat(e,") > maxMapLengthLength (").concat(this.maxMapLength,")"));this.stack.push({type:1,size:e,key:null,readCount:0,map:{}})},e.prototype.pushArrayState=function(e){if(e>this.maxArrayLength)throw new $n("Max length exceeded: array length (".concat(e,") > maxArrayLength (").concat(this.maxArrayLength,")"));this.stack.push({type:0,size:e,array:new Array(e),position:0})},e.prototype.decodeUtf8String=function(e,t){var n;if(e>this.maxStrLength)throw new $n("Max length exceeded: UTF-8 byte length (".concat(e,") > maxStrLength (").concat(this.maxStrLength,")"));if(this.bytes.byteLengthMn?function(e,t,n){var r=e.subarray(t,t+n);return Un.decode(r)}(this.bytes,o,e):Pn(this.bytes,o,e),this.pos+=t+e,r},e.prototype.stateIsMapKey=function(){return this.stack.length>0&&1===this.stack[this.stack.length-1].type},e.prototype.decodeBinary=function(e,t){if(e>this.maxBinLength)throw new $n("Max length exceeded: bin length (".concat(e,") > maxBinLength (").concat(this.maxBinLength,")"));if(!this.hasRemaining(e+t))throw Gn;var n=this.pos+t,r=this.bytes.subarray(n,n+e);return this.pos+=t+e,r},e.prototype.decodeExtension=function(e,t){if(e>this.maxExtLength)throw new $n("Max length exceeded: ext length (".concat(e,") > maxExtLength (").concat(this.maxExtLength,")"));var n=this.view.getInt8(this.pos+t),r=this.decodeBinary(e,t+1);return this.extensionCodec.decode(r,n,this.context)},e.prototype.lookU8=function(){return this.view.getUint8(this.pos)},e.prototype.lookU16=function(){return this.view.getUint16(this.pos)},e.prototype.lookU32=function(){return this.view.getUint32(this.pos)},e.prototype.readU8=function(){var e=this.view.getUint8(this.pos);return this.pos++,e},e.prototype.readI8=function(){var e=this.view.getInt8(this.pos);return this.pos++,e},e.prototype.readU16=function(){var e=this.view.getUint16(this.pos);return this.pos+=2,e},e.prototype.readI16=function(){var e=this.view.getInt16(this.pos);return this.pos+=2,e},e.prototype.readU32=function(){var e=this.view.getUint32(this.pos);return this.pos+=4,e},e.prototype.readI32=function(){var e=this.view.getInt32(this.pos);return this.pos+=4,e},e.prototype.readU64=function(){var e,t,n=(e=this.view,t=this.pos,4294967296*e.getUint32(t)+e.getUint32(t+4));return this.pos+=8,n},e.prototype.readI64=function(){var e=kn(this.view,this.pos);return this.pos+=8,e},e.prototype.readF32=function(){var e=this.view.getFloat32(this.pos);return this.pos+=4,e},e.prototype.readF64=function(){var e=this.view.getFloat64(this.pos);return this.pos+=8,e},e}();class er{static write(e){let t=e.byteLength||e.length;const n=[];do{let e=127&t;t>>=7,t>0&&(e|=128),n.push(e)}while(t>0);t=e.byteLength||e.length;const r=new Uint8Array(n.length+t);return r.set(n,0),r.set(e,n.length),r.buffer}static parse(e){const t=[],n=new Uint8Array(e),r=[0,7,14,21,28];for(let o=0;o7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=o+i+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(o+i,o+i+a):n.subarray(o+i,o+i+a)),o=o+i+a}return t}}const tr=new Uint8Array([145,qt.Ping]);class nr{constructor(e){this.name="messagepack",this.version=2,this.transferFormat=ln.Binary,this._errorResult=1,this._voidResult=2,this._nonVoidResult=3,e=e||{},this._encoder=new jn(e.extensionCodec,e.context,e.maxDepth,e.initialBufferSize,e.sortKeys,e.forceFloat32,e.ignoreUndefined,e.forceIntegerToFloat),this._decoder=new Zn(e.extensionCodec,e.context,e.maxStrLength,e.maxBinLength,e.maxArrayLength,e.maxMapLength,e.maxExtLength)}parseMessages(e,t){if(!(n=e)||"undefined"==typeof ArrayBuffer||!(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer.");var n;null===t&&(t=St.instance);const r=er.parse(e),o=[];for(const e of r){const n=this._parseMessage(e,t);n&&o.push(n)}return o}writeMessage(e){switch(e.type){case qt.Invocation:return this._writeInvocation(e);case qt.StreamInvocation:return this._writeStreamInvocation(e);case qt.StreamItem:return this._writeStreamItem(e);case qt.Completion:return this._writeCompletion(e);case qt.Ping:return er.write(tr);case qt.CancelInvocation:return this._writeCancelInvocation(e);case qt.Close:return this._writeClose();case qt.Ack:return this._writeAck(e);case qt.Sequence:return this._writeSequence(e);default:throw new Error("Invalid message type.")}}_parseMessage(e,t){if(0===e.length)throw new Error("Invalid payload.");const n=this._decoder.decode(e);if(0===n.length||!(n instanceof Array))throw new Error("Invalid payload.");const r=n[0];switch(r){case qt.Invocation:return this._createInvocationMessage(this._readHeaders(n),n);case qt.StreamItem:return this._createStreamItemMessage(this._readHeaders(n),n);case qt.Completion:return this._createCompletionMessage(this._readHeaders(n),n);case qt.Ping:return this._createPingMessage(n);case qt.Close:return this._createCloseMessage(n);case qt.Ack:return this._createAckMessage(n);case qt.Sequence:return this._createSequenceMessage(n);default:return t.log(bt.Information,"Unknown message type '"+r+"' ignored."),null}}_createCloseMessage(e){if(e.length<2)throw new Error("Invalid payload for Close message.");return{allowReconnect:e.length>=3?e[2]:void 0,error:e[1],type:qt.Close}}_createPingMessage(e){if(e.length<1)throw new Error("Invalid payload for Ping message.");return{type:qt.Ping}}_createInvocationMessage(e,t){if(t.length<5)throw new Error("Invalid payload for Invocation message.");const n=t[2];return n?{arguments:t[4],headers:e,invocationId:n,streamIds:[],target:t[3],type:qt.Invocation}:{arguments:t[4],headers:e,streamIds:[],target:t[3],type:qt.Invocation}}_createStreamItemMessage(e,t){if(t.length<4)throw new Error("Invalid payload for StreamItem message.");return{headers:e,invocationId:t[2],item:t[3],type:qt.StreamItem}}_createCompletionMessage(e,t){if(t.length<4)throw new Error("Invalid payload for Completion message.");const n=t[3];if(n!==this._voidResult&&t.length<5)throw new Error("Invalid payload for Completion message.");let r,o;switch(n){case this._errorResult:r=t[4];break;case this._nonVoidResult:o=t[4]}return{error:r,headers:e,invocationId:t[2],result:o,type:qt.Completion}}_createAckMessage(e){if(e.length<1)throw new Error("Invalid payload for Ack message.");return{sequenceId:e[1],type:qt.Ack}}_createSequenceMessage(e){if(e.length<1)throw new Error("Invalid payload for Sequence message.");return{sequenceId:e[1],type:qt.Sequence}}_writeInvocation(e){let t;return t=e.streamIds?this._encoder.encode([qt.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments,e.streamIds]):this._encoder.encode([qt.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments]),er.write(t.slice())}_writeStreamInvocation(e){let t;return t=e.streamIds?this._encoder.encode([qt.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments,e.streamIds]):this._encoder.encode([qt.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments]),er.write(t.slice())}_writeStreamItem(e){const t=this._encoder.encode([qt.StreamItem,e.headers||{},e.invocationId,e.item]);return er.write(t.slice())}_writeCompletion(e){const t=e.error?this._errorResult:void 0!==e.result?this._nonVoidResult:this._voidResult;let n;switch(t){case this._errorResult:n=this._encoder.encode([qt.Completion,e.headers||{},e.invocationId,t,e.error]);break;case this._voidResult:n=this._encoder.encode([qt.Completion,e.headers||{},e.invocationId,t]);break;case this._nonVoidResult:n=this._encoder.encode([qt.Completion,e.headers||{},e.invocationId,t,e.result])}return er.write(n.slice())}_writeCancelInvocation(e){const t=this._encoder.encode([qt.CancelInvocation,e.headers||{},e.invocationId]);return er.write(t.slice())}_writeClose(){const e=this._encoder.encode([qt.Close,null]);return er.write(e.slice())}_writeAck(e){const t=this._encoder.encode([qt.Ack,e.sequenceId]);return er.write(t.slice())}_writeSequence(e){const t=this._encoder.encode([qt.Sequence,e.sequenceId]);return er.write(t.slice())}_readHeaders(e){const t=e[1];if("object"!=typeof t)throw new Error("Invalid headers.");return t}}const rr="function"==typeof TextDecoder?new TextDecoder("utf-8"):null,or=rr?rr.decode.bind(rr):function(e){let t=0;const n=e.length,r=[],o=[];for(;t65535&&(o-=65536,r.push(o>>>10&1023|55296),o=56320|1023&o),r.push(o)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")},sr=Math.pow(2,32),ir=Math.pow(2,21)-1;function ar(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24}function cr(e,t){return e[t]+(e[t+1]<<8)+(e[t+2]<<16)+(e[t+3]<<24>>>0)}function lr(e,t){const n=cr(e,t+4);if(n>ir)throw new Error(`Cannot read uint64 with high order part ${n}, because the result would exceed Number.MAX_SAFE_INTEGER.`);return n*sr+cr(e,t)}class hr{constructor(e){this.batchData=e;const t=new fr(e);this.arrayRangeReader=new gr(e),this.arrayBuilderSegmentReader=new mr(e),this.diffReader=new dr(e),this.editReader=new ur(e,t),this.frameReader=new pr(e,t)}updatedComponents(){return ar(this.batchData,this.batchData.length-20)}referenceFrames(){return ar(this.batchData,this.batchData.length-16)}disposedComponentIds(){return ar(this.batchData,this.batchData.length-12)}disposedEventHandlerIds(){return ar(this.batchData,this.batchData.length-8)}updatedComponentsEntry(e,t){const n=e+4*t;return ar(this.batchData,n)}referenceFramesEntry(e,t){return e+20*t}disposedComponentIdsEntry(e,t){const n=e+4*t;return ar(this.batchData,n)}disposedEventHandlerIdsEntry(e,t){const n=e+8*t;return lr(this.batchData,n)}}class dr{constructor(e){this.batchDataUint8=e}componentId(e){return ar(this.batchDataUint8,e)}edits(e){return e+4}editsEntry(e,t){return e+16*t}}class ur{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}editType(e){return ar(this.batchDataUint8,e)}siblingIndex(e){return ar(this.batchDataUint8,e+4)}newTreeIndex(e){return ar(this.batchDataUint8,e+8)}moveToSiblingIndex(e){return ar(this.batchDataUint8,e+8)}removedAttributeName(e){const t=ar(this.batchDataUint8,e+12);return this.stringReader.readString(t)}}class pr{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}frameType(e){return ar(this.batchDataUint8,e)}subtreeLength(e){return ar(this.batchDataUint8,e+4)}elementReferenceCaptureId(e){const t=ar(this.batchDataUint8,e+4);return this.stringReader.readString(t)}componentId(e){return ar(this.batchDataUint8,e+8)}elementName(e){const t=ar(this.batchDataUint8,e+8);return this.stringReader.readString(t)}textContent(e){const t=ar(this.batchDataUint8,e+4);return this.stringReader.readString(t)}markupContent(e){const t=ar(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeName(e){const t=ar(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeValue(e){const t=ar(this.batchDataUint8,e+8);return this.stringReader.readString(t)}attributeEventHandlerId(e){return lr(this.batchDataUint8,e+12)}}class fr{constructor(e){this.batchDataUint8=e,this.stringTableStartIndex=ar(e,e.length-4)}readString(e){if(-1===e)return null;{const n=ar(this.batchDataUint8,this.stringTableStartIndex+4*e),r=function(e,t){let n=0,r=0;for(let o=0;o<4;o++){const s=e[t+o];if(n|=(127&s)<this.nextBatchId)return this.fatalError?(this.logger.log(st.Debug,`Received a new batch ${e} but errored out on a previous batch ${this.nextBatchId-1}`),void await n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())):void this.logger.log(st.Debug,`Waiting for batch ${this.nextBatchId}. Batch ${e} not processed.`);try{this.nextBatchId++,this.logger.log(st.Debug,`Applying batch ${e}.`),function(e,t){const n=ge[e];if(!n)throw new Error(`There is no browser renderer with ID ${e}.`);const r=t.arrayRangeReader,o=t.updatedComponents(),s=r.values(o),i=r.count(o),a=t.referenceFrames(),c=r.values(a),l=t.diffReader;for(let e=0;e{e.onclick=function(e){location.reload(),e.preventDefault()}})),document.querySelectorAll("#blazor-error-ui .dismiss").forEach((e=>{e.onclick=function(e){const t=document.querySelector("#blazor-error-ui");t&&(t.style.display="none"),e.preventDefault()}})))}class kr{constructor(t,n,r,o){this._firstUpdate=!0,this._renderingFailed=!1,this._disposed=!1,this._circuitId=void 0,this._applicationState=n,this._componentManager=t,this._options=r,this._logger=o,this._renderQueue=new vr(this._logger),this._dispatcher=e.attachDispatcher(this)}start(){if(this.isDisposedOrDisposing())throw new Error("Cannot start a disposed circuit.");return this._startPromise||(this._startPromise=this.startCore()),this._startPromise}updateRootComponents(e){var t,n;return this._firstUpdate?(this._firstUpdate=!1,null===(t=this._connection)||void 0===t?void 0:t.send("UpdateRootComponents",e,this._applicationState)):null===(n=this._connection)||void 0===n?void 0:n.send("UpdateRootComponents",e,"")}async startCore(){if(this._connection=await this.startConnection(),this._connection.state!==Jt.Connected)return!1;const e=JSON.stringify(this._componentManager.initialComponents.map((e=>{return t=e,{...t,start:void 0,end:void 0};var t})));if(this._circuitId=await this._connection.invoke("StartCircuit",Ue.getBaseURI(),Ue.getLocationHref(),e,this._applicationState||""),!this._circuitId)return!1;for(const e of this._options.circuitHandlers)e.onCircuitOpened&&e.onCircuitOpened();return!0}async startConnection(){var e,t;const n=new nr;n.name="blazorpack";const r=(new wn).withUrl("_blazor").withHubProtocol(n);this._options.configureSignalR(r);const o=r.build();o.on("JS.AttachComponent",((e,t)=>function(e,t,n,r){let o=ge[e];o||(o=new de(e),ge[e]=o),o.attachRootComponentToLogicalElement(n,t,!1)}(_n.Server,this.resolveElement(t),e))),o.on("JS.BeginInvokeJS",this._dispatcher.beginInvokeJSFromDotNet.bind(this._dispatcher)),o.on("JS.EndInvokeDotNet",this._dispatcher.endInvokeDotNetFromJS.bind(this._dispatcher)),o.on("JS.ReceiveByteArray",this._dispatcher.receiveByteArray.bind(this._dispatcher)),o.on("JS.BeginTransmitStream",(e=>{const t=new ReadableStream({start:t=>{o.stream("SendDotNetStreamToJS",e).subscribe({next:e=>t.enqueue(e),complete:()=>t.close(),error:e=>t.error(e)})}});this._dispatcher.supplyDotNetStream(e,t)})),o.on("JS.RenderBatch",(async(e,t)=>{var n,r;this._logger.log(bt.Debug,`Received render batch with id ${e} and ${t.byteLength} bytes.`),await this._renderQueue.processBatch(e,t,this._connection),null===(r=(n=this._componentManager).onAfterRenderBatch)||void 0===r||r.call(n,_n.Server)})),o.on("JS.EndUpdateRootComponents",(e=>{var t,n;null===(n=(t=this._componentManager).onAfterUpdateRootComponents)||void 0===n||n.call(t,e)})),o.on("JS.EndLocationChanging",ot._internal.navigationManager.endLocationChanging),o.onclose((e=>{this._interopMethodsForReconnection=function(e){const t=S.get(e);if(!t)throw new Error(`Interop methods are not registered for renderer ${e}`);return S.delete(e),t}(_n.Server),this._disposed||this._renderingFailed||this._options.reconnectionHandler.onConnectionDown(this._options.reconnectionOptions,e)})),o.on("JS.Error",(e=>{this._renderingFailed=!0,this.unhandledError(e),Ir()}));try{await o.start()}catch(e){if(this.unhandledError(e),"FailedToNegotiateWithServerError"===e.errorType)throw e;Ir(),e.innerErrors&&(e.innerErrors.some((e=>"UnsupportedTransportError"===e.errorType&&e.transport===cn.WebSockets))?this._logger.log(bt.Error,"Unable to connect, please ensure you are using an updated browser that supports WebSockets."):e.innerErrors.some((e=>"FailedToStartTransportError"===e.errorType&&e.transport===cn.WebSockets))?this._logger.log(bt.Error,"Unable to connect, please ensure WebSockets are available. A VPN or proxy may be blocking the connection."):e.innerErrors.some((e=>"DisabledTransportError"===e.errorType&&e.transport===cn.LongPolling))&&this._logger.log(bt.Error,"Unable to initiate a SignalR connection to the server. This might be because the server is not configured to support WebSockets. For additional details, visit https://aka.ms/blazor-server-websockets-error."))}return(null===(t=null===(e=o.connection)||void 0===e?void 0:e.features)||void 0===t?void 0:t.inherentKeepAlive)&&this._logger.log(bt.Warning,"Failed to connect via WebSockets, using the Long Polling fallback transport. This may be due to a VPN or proxy blocking the connection. To troubleshoot this, visit https://aka.ms/blazor-server-using-fallback-long-polling."),o}async disconnect(){var e;await(null===(e=this._connection)||void 0===e?void 0:e.stop())}async reconnect(){if(!this._circuitId)throw new Error("Circuit host not initialized.");return this._connection.state===Jt.Connected||(this._connection=await this.startConnection(),this._interopMethodsForReconnection&&(I(_n.Server,this._interopMethodsForReconnection),this._interopMethodsForReconnection=void 0),!!await this._connection.invoke("ConnectCircuit",this._circuitId)&&(this._options.reconnectionHandler.onConnectionUp(),!0))}beginInvokeDotNetFromJS(e,t,n,r,o){this.throwIfDispatchingWhenDisposed(),this._connection.send("BeginInvokeDotNetFromJS",e?e.toString():null,t,n,r||0,o)}endInvokeJSFromDotNet(e,t,n){this.throwIfDispatchingWhenDisposed(),this._connection.send("EndInvokeJSFromDotNet",e,t,n)}sendByteArray(e,t){this.throwIfDispatchingWhenDisposed(),this._connection.send("ReceiveByteArray",e,t)}throwIfDispatchingWhenDisposed(){if(this._disposed)throw new Error("The circuit associated with this dispatcher is no longer available.")}sendLocationChanged(e,t,n){return this._connection.send("OnLocationChanged",e,t,n)}sendLocationChanging(e,t,n,r){return this._connection.send("OnLocationChanging",e,t,n,r)}sendJsDataStream(e,t,n){return function(e,t,n,r){setTimeout((async()=>{let o=5,s=(new Date).valueOf();try{const i=t instanceof Blob?t.size:t.byteLength;let a=0,c=0;for(;a1)await e.send("ReceiveJSDataChunk",n,c,h,null);else{if(!await e.invoke("ReceiveJSDataChunk",n,c,h,null))break;const t=(new Date).valueOf(),r=t-s;s=t,o=Math.max(1,Math.round(500/Math.max(1,r)))}a+=l,c++}}catch(t){await e.send("ReceiveJSDataChunk",n,-1,null,t.toString())}}),0)}(this._connection,e,t,n)}resolveElement(e){const t=function(e){const t=f.get(e);if(t)return f.delete(e),t}(e);if(t)return O(t,!0);const n=Number.parseInt(e);if(!Number.isNaN(n))return function(e){const{start:t,end:n}=e,r=t[$];if(r){if(r!==e)throw new Error("The start component comment was already associated with another component descriptor.");return t}const o=t.parentNode;if(!o)throw new Error(`Comment not connected to the DOM ${t.textContent}`);const s=O(o,!0),i=K(s);t[L]=s,t[$]=e;const a=O(t);if(n){const e=K(a),r=Array.prototype.indexOf.call(i,a)+1;let o=null;for(;o!==n;){const n=i.splice(r,1)[0];if(!n)throw new Error("Could not find the end component comment in the parent logical node list");n[L]=t,e.push(n),o=n}}return a}(this._componentManager.resolveRootComponent(n));throw new Error(`Invalid sequence number or identifier '${e}'.`)}getRootComponentManager(){return this._componentManager}unhandledError(e){this._logger.log(bt.Error,e),this.disconnect()}getDisconnectFormData(){const e=new FormData,t=this._circuitId;return e.append("circuitId",t),e}didRenderingFail(){return this._renderingFailed}isDisposedOrDisposing(){return void 0!==this._disposePromise}sendDisconnectBeacon(){if(this._disposed)return;const e=this.getDisconnectFormData();this._disposed=navigator.sendBeacon("_blazor/disconnect",e)}dispose(){return this._disposePromise||(this._disposePromise=this.disposeCore()),this._disposePromise}async disposeCore(){var e;if(!this._startPromise)return void(this._disposed=!0);await this._startPromise,this._disposed=!0,null===(e=this._connection)||void 0===e||e.stop();const t=this.getDisconnectFormData();fetch("_blazor/disconnect",{method:"POST",body:t});for(const e of this._options.circuitHandlers)e.onCircuitClosed&&e.onCircuitClosed()}}class Tr{constructor(e,t,n,r){this.maxRetries=t,this.document=n,this.logger=r,this.modal=this.document.createElement("div"),this.modal.id=e,this.maxRetries=t,this.modal.style.cssText=["position: fixed","top: 0","right: 0","bottom: 0","left: 0","z-index: 1050","display: none","overflow: hidden","background-color: #fff","opacity: 0.8","text-align: center","font-weight: bold","transition: visibility 0s linear 500ms"].join(";"),this.message=this.document.createElement("h5"),this.message.style.cssText="margin-top: 20px",this.button=this.document.createElement("button"),this.button.style.cssText="margin:5px auto 5px",this.button.textContent="Retry";const o=this.document.createElement("a");o.addEventListener("click",(()=>location.reload())),o.textContent="reload",this.reloadParagraph=this.document.createElement("p"),this.reloadParagraph.textContent="Alternatively, ",this.reloadParagraph.appendChild(o),this.modal.appendChild(this.message),this.modal.appendChild(this.button),this.modal.appendChild(this.reloadParagraph),this.loader=this.getLoader(),this.message.after(this.loader),this.button.addEventListener("click",(async()=>{this.show();try{await ot.reconnect()||this.rejected()}catch(e){this.logger.log(st.Error,e),this.failed()}}))}show(){this.document.contains(this.modal)||this.document.body.appendChild(this.modal),this.modal.style.display="block",this.loader.style.display="inline-block",this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.textContent="Attempting to reconnect to the server...",this.modal.style.visibility="hidden",setTimeout((()=>{this.modal.style.visibility="visible"}),0)}update(e){this.message.textContent=`Attempting to reconnect to the server: ${e} of ${this.maxRetries}`}hide(){this.modal.style.display="none"}failed(){this.button.style.display="block",this.reloadParagraph.style.display="none",this.loader.style.display="none";const e=this.document.createTextNode("Reconnection failed. Try "),t=this.document.createElement("a");t.textContent="reloading",t.setAttribute("href",""),t.addEventListener("click",(()=>location.reload()));const n=this.document.createTextNode(" the page if you're unable to reconnect.");this.message.replaceChildren(e,t,n)}rejected(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.loader.style.display="none";const e=this.document.createTextNode("Could not reconnect to the server. "),t=this.document.createElement("a");t.textContent="Reload",t.setAttribute("href",""),t.addEventListener("click",(()=>location.reload()));const n=this.document.createTextNode(" the page to restore functionality.");this.message.replaceChildren(e,t,n)}getLoader(){const e=this.document.createElement("div");return e.style.cssText=["border: 0.3em solid #f3f3f3","border-top: 0.3em solid #3498db","border-radius: 50%","width: 2em","height: 2em","display: inline-block"].join(";"),e.animate([{transform:"rotate(0deg)"},{transform:"rotate(360deg)"}],{duration:2e3,iterations:1/0}),e}}class Dr{constructor(e,t,n){this.dialog=e,this.maxRetries=t,this.document=n,this.document=n;const r=this.document.getElementById(Dr.MaxRetriesId);r&&(r.innerText=this.maxRetries.toString())}show(){this.removeClasses(),this.dialog.classList.add(Dr.ShowClassName)}update(e){const t=this.document.getElementById(Dr.CurrentAttemptId);t&&(t.innerText=e.toString())}hide(){this.removeClasses(),this.dialog.classList.add(Dr.HideClassName)}failed(){this.removeClasses(),this.dialog.classList.add(Dr.FailedClassName)}rejected(){this.removeClasses(),this.dialog.classList.add(Dr.RejectedClassName)}removeClasses(){this.dialog.classList.remove(Dr.ShowClassName,Dr.HideClassName,Dr.FailedClassName,Dr.RejectedClassName)}}Dr.ShowClassName="components-reconnect-show",Dr.HideClassName="components-reconnect-hide",Dr.FailedClassName="components-reconnect-failed",Dr.RejectedClassName="components-reconnect-rejected",Dr.MaxRetriesId="components-reconnect-max-retries",Dr.CurrentAttemptId="components-reconnect-current-attempt";class Rr{constructor(e,t,n){this._currentReconnectionProcess=null,this._logger=e,this._reconnectionDisplay=t,this._reconnectCallback=n||ot.reconnect}onConnectionDown(e,t){if(!this._reconnectionDisplay){const t=document.getElementById(e.dialogId);this._reconnectionDisplay=t?new Dr(t,e.maxRetries,document):new Tr(e.dialogId,e.maxRetries,document,this._logger)}this._currentReconnectionProcess||(this._currentReconnectionProcess=new xr(e,this._logger,this._reconnectCallback,this._reconnectionDisplay))}onConnectionUp(){this._currentReconnectionProcess&&(this._currentReconnectionProcess.dispose(),this._currentReconnectionProcess=null)}}class xr{constructor(e,t,n,r){this.logger=t,this.reconnectCallback=n,this.isDisposed=!1,this.reconnectDisplay=r,this.reconnectDisplay.show(),this.attemptPeriodicReconnection(e)}dispose(){this.isDisposed=!0,this.reconnectDisplay.hide()}async attemptPeriodicReconnection(e){for(let t=0;txr.MaximumFirstRetryInterval?xr.MaximumFirstRetryInterval:e.retryIntervalMilliseconds;if(await this.delay(n),this.isDisposed)break;try{return await this.reconnectCallback()?void 0:void this.reconnectDisplay.rejected()}catch(e){this.logger.log(st.Error,e)}}this.reconnectDisplay.failed()}delay(e){return new Promise((t=>setTimeout(t,e)))}}xr.MaximumFirstRetryInterval=3e3;class Ar{constructor(e=!0,t,n,r=0){this.singleRuntime=e,this.logger=t,this.webRendererId=r,this.afterStartedCallbacks=[],n&&this.afterStartedCallbacks.push(...n)}async importInitializersAsync(e,t){await Promise.all(e.map((e=>async function(e,n){const r=function(e){const t=document.baseURI;return t.endsWith("/")?`${t}${e}`:`${t}/${e}`}(n),o=await import(r);if(void 0!==o){if(e.singleRuntime){const{beforeStart:n,afterStarted:r,beforeWebAssemblyStart:i,afterWebAssemblyStarted:a,beforeServerStart:c,afterServerStarted:l}=o;let h=n;e.webRendererId===_n.Server&&c&&(h=c),e.webRendererId===_n.WebAssembly&&i&&(h=i);let d=r;return e.webRendererId===_n.Server&&l&&(d=l),e.webRendererId===_n.WebAssembly&&a&&(d=a),s(e,h,d,t)}return function(e,t,n){var o;const i=n[0],{beforeStart:a,afterStarted:c,beforeWebStart:l,afterWebStarted:h,beforeWebAssemblyStart:d,afterWebAssemblyStarted:u,beforeServerStart:p,afterServerStarted:f}=t,g=!(l||h||d||u||p||f||!a&&!c),m=g&&i.enableClassicInitializers;if(g&&!i.enableClassicInitializers)null===(o=e.logger)||void 0===o||o.log(st.Warning,`Initializer '${r}' will be ignored because multiple runtimes are available. use 'before(web|webAssembly|server)Start' and 'after(web|webAssembly|server)Started?' instead.)`);else if(m)return s(e,a,c,n);if(function(e){e.webAssembly?e.webAssembly.initializers||(e.webAssembly.initializers={beforeStart:[],afterStarted:[]}):e.webAssembly={initializers:{beforeStart:[],afterStarted:[]}},e.circuit?e.circuit.initializers||(e.circuit.initializers={beforeStart:[],afterStarted:[]}):e.circuit={initializers:{beforeStart:[],afterStarted:[]}}}(i),d&&i.webAssembly.initializers.beforeStart.push(d),u&&i.webAssembly.initializers.afterStarted.push(u),p&&i.circuit.initializers.beforeStart.push(p),f&&i.circuit.initializers.afterStarted.push(f),h&&e.afterStartedCallbacks.push(h),l)return l(i)}(e,o,t)}function s(e,t,n,r){if(n&&e.afterStartedCallbacks.push(n),t)return t(...r)}}(this,e))))}async invokeAfterStartedCallbacks(e){const t=function(e){var t;return null===(t=C.get(e))||void 0===t?void 0:t[1]}(this.webRendererId);t&&await t,await Promise.all(this.afterStartedCallbacks.map((t=>t(e))))}}function Pr(e){if(void 0!==Er)throw new Error("Blazor Server has already started.");return Er=new Promise(Nr.bind(null,e)),Er}async function Nr(e,t,n){await yr;const r=await async function(e){if(e.initializers)return await Promise.all(e.initializers.beforeStart.map((t=>t(e)))),new Ar(!1,void 0,e.initializers.afterStarted,_n.Server);const t=await fetch("_blazor/initializers",{method:"GET",credentials:"include",cache:"no-cache"}),n=await t.json(),r=new Ar(!0,void 0,void 0,_n.Server);return await r.importInitializersAsync(n,[e]),r}(br);var o;if(o=document,wr=dt(o,ht)||"",Sr=new lt(br.logLevel),_r=new kr(e,wr,br,Sr),Sr.log(st.Information,"Starting up Blazor server-side application."),ot.reconnect=async()=>!(_r.didRenderingFail()||!await _r.reconnect()&&(Sr.log(st.Information,"Reconnection attempt to the circuit was rejected by the server. This may indicate that the associated state is no longer available on the server."),1)),ot.defaultReconnectionHandler=new Rr(Sr),br.reconnectionHandler=br.reconnectionHandler||ot.defaultReconnectionHandler,ot._internal.navigationManager.listenForNavigationEvents(_n.Server,((e,t,n)=>_r.sendLocationChanged(e,t,n)),((e,t,n,r)=>_r.sendLocationChanging(e,t,n,r))),ot._internal.forceCloseConnection=()=>_r.disconnect(),ot._internal.sendJSDataStream=(e,t,n)=>_r.sendJsDataStream(e,t,n),!await _r.start())return Sr.log(st.Error,"Failed to start the circuit."),void t();const s=()=>{_r.sendDisconnectBeacon()};ot.disconnect=s,window.addEventListener("unload",s,{capture:!1,once:!0}),Sr.log(st.Information,"Blazor server-side application started."),r.invokeAfterStartedCallbacks(ot),t()}class Ur{constructor(e){this.initialComponents=e}resolveRootComponent(e){return this.initialComponents[e]}}class Mr{constructor(){this._eventListeners=new Map}static create(e){const t=new Mr;return e.addEventListener=t.addEventListener.bind(t),e.removeEventListener=t.removeEventListener.bind(t),t}addEventListener(e,t){let n=this._eventListeners.get(e);n||(n=new Set,this._eventListeners.set(e,n)),n.add(t)}removeEventListener(e,t){var n;null===(n=this._eventListeners.get(e))||void 0===n||n.delete(t)}dispatchEvent(e,t){const n=this._eventListeners.get(e);if(!n)return;const r={...t,type:e};for(const e of n)e(r)}}let Br=!1;function Lr(e){if(Br)throw new Error("Blazor has already started.");Br=!0;const t=it(e);!function(e){if(br)throw new Error("Circuit options have already been configured.");if(br)throw new Error("WebAssembly options have already been configured.");yr=async function(e){const t=await e;br=it(t)}(e)}(Promise.resolve(t||{})),Mr.create(ot);const n=function(e){return ut(e,"server").sort(((e,t)=>e.sequence-t.sequence))}(document);return Pr(new Ur(n))}ot.start=Lr,window.DotNet=e,document&&document.currentScript&&"false"!==document.currentScript.getAttribute("autostart")&&Lr()})()})(); \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/dist/Release/blazor.web.js dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/dist/Release/blazor.web.js --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/dist/Release/blazor.web.js 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/dist/Release/blazor.web.js 2023-11-13 13:20:34.000000000 +0000 @@ -1 +1 @@ -(()=>{var e={778:()=>{},77:()=>{},203:()=>{},200:()=>{},628:()=>{},321:()=>{}},t={};function n(o){var r=t[o];if(void 0!==r)return r.exports;var s=t[o]={exports:{}};return e[o](s,s.exports,n),s.exports}n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),(()=>{"use strict";var e,t,o;!function(e){const t=[],n="__jsObjectId",o="__dotNetObject",r="__byte[]",s="__dotNetStream",i="__jsStreamReferenceLength";let a,c;class l{constructor(e){this._jsObject=e,this._cachedFunctions=new Map}findFunction(e){const t=this._cachedFunctions.get(e);if(t)return t;let n,o=this._jsObject;if(e.split(".").forEach((t=>{if(!(t in o))throw new Error(`Could not find '${e}' ('${t}' was undefined).`);n=o,o=o[t]})),o instanceof Function)return o=o.bind(n),this._cachedFunctions.set(e,o),o;throw new Error(`The value '${e}' is not a function.`)}getWrappedObject(){return this._jsObject}}const h={0:new l(window)};h[0]._cachedFunctions.set("import",(e=>("string"==typeof e&&e.startsWith("./")&&(e=new URL(e.substr(2),document.baseURI).toString()),import(e))));let d,u=1;function p(e){t.push(e)}function f(e){if(e&&"object"==typeof e){h[u]=new l(e);const t={[n]:u};return u++,t}throw new Error(`Cannot create a JSObjectReference from the value '${e}'.`)}function g(e){let t=-1;if(e instanceof ArrayBuffer&&(e=new Uint8Array(e)),e instanceof Blob)t=e.size;else{if(!(e.buffer instanceof ArrayBuffer))throw new Error("Supplied value is not a typed array or blob.");if(void 0===e.byteLength)throw new Error(`Cannot create a JSStreamReference from the value '${e}' as it doesn't have a byteLength.`);t=e.byteLength}const o={[i]:t};try{const t=f(e);o[n]=t[n]}catch(t){throw new Error(`Cannot create a JSStreamReference from the value '${e}'.`)}return o}function m(e,n){c=e;const o=n?JSON.parse(n,((e,n)=>t.reduce(((t,n)=>n(e,t)),n))):null;return c=void 0,o}function v(){if(void 0===a)throw new Error("No call dispatcher has been set.");if(null===a)throw new Error("There are multiple .NET runtimes present, so a default dispatcher could not be resolved. Use DotNetObject to invoke .NET instance methods.");return a}e.attachDispatcher=function(e){const t=new y(e);return void 0===a?a=t:a&&(a=null),t},e.attachReviver=p,e.invokeMethod=function(e,t,...n){return v().invokeDotNetStaticMethod(e,t,...n)},e.invokeMethodAsync=function(e,t,...n){return v().invokeDotNetStaticMethodAsync(e,t,...n)},e.createJSObjectReference=f,e.createJSStreamReference=g,e.disposeJSObjectReference=function(e){const t=e&&e[n];"number"==typeof t&&_(t)},function(e){e[e.Default=0]="Default",e[e.JSObjectReference=1]="JSObjectReference",e[e.JSStreamReference=2]="JSStreamReference",e[e.JSVoidResult=3]="JSVoidResult"}(d=e.JSCallResultType||(e.JSCallResultType={}));class y{constructor(e){this._dotNetCallDispatcher=e,this._byteArraysToBeRevived=new Map,this._pendingDotNetToJSStreams=new Map,this._pendingAsyncCalls={},this._nextAsyncCallId=1}getDotNetCallDispatcher(){return this._dotNetCallDispatcher}invokeJSFromDotNet(e,t,n,o){const r=m(this,t),s=I(b(e,o)(...r||[]),n);return null==s?null:T(this,s)}beginInvokeJSFromDotNet(e,t,n,o,r){const s=new Promise((e=>{const o=m(this,n);e(b(t,r)(...o||[]))}));e&&s.then((t=>T(this,[e,!0,I(t,o)]))).then((t=>this._dotNetCallDispatcher.endInvokeJSFromDotNet(e,!0,t)),(t=>this._dotNetCallDispatcher.endInvokeJSFromDotNet(e,!1,JSON.stringify([e,!1,w(t)]))))}endInvokeDotNetFromJS(e,t,n){const o=t?m(this,n):new Error(n);this.completePendingCall(parseInt(e,10),t,o)}invokeDotNetStaticMethod(e,t,...n){return this.invokeDotNetMethod(e,t,null,n)}invokeDotNetStaticMethodAsync(e,t,...n){return this.invokeDotNetMethodAsync(e,t,null,n)}invokeDotNetMethod(e,t,n,o){if(this._dotNetCallDispatcher.invokeDotNetFromJS){const r=T(this,o),s=this._dotNetCallDispatcher.invokeDotNetFromJS(e,t,n,r);return s?m(this,s):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeDotNetMethodAsync instead.")}invokeDotNetMethodAsync(e,t,n,o){if(e&&n)throw new Error(`For instance method calls, assemblyName should be null. Received '${e}'.`);const r=this._nextAsyncCallId++,s=new Promise(((e,t)=>{this._pendingAsyncCalls[r]={resolve:e,reject:t}}));try{const s=T(this,o);this._dotNetCallDispatcher.beginInvokeDotNetFromJS(r,e,t,n,s)}catch(e){this.completePendingCall(r,!1,e)}return s}receiveByteArray(e,t){this._byteArraysToBeRevived.set(e,t)}processByteArray(e){const t=this._byteArraysToBeRevived.get(e);return t?(this._byteArraysToBeRevived.delete(e),t):null}supplyDotNetStream(e,t){if(this._pendingDotNetToJSStreams.has(e)){const n=this._pendingDotNetToJSStreams.get(e);this._pendingDotNetToJSStreams.delete(e),n.resolve(t)}else{const n=new C;n.resolve(t),this._pendingDotNetToJSStreams.set(e,n)}}getDotNetStreamPromise(e){let t;if(this._pendingDotNetToJSStreams.has(e))t=this._pendingDotNetToJSStreams.get(e).streamPromise,this._pendingDotNetToJSStreams.delete(e);else{const n=new C;this._pendingDotNetToJSStreams.set(e,n),t=n.streamPromise}return t}completePendingCall(e,t,n){if(!this._pendingAsyncCalls.hasOwnProperty(e))throw new Error(`There is no pending async call with ID ${e}.`);const o=this._pendingAsyncCalls[e];delete this._pendingAsyncCalls[e],t?o.resolve(n):o.reject(n)}}function w(e){return e instanceof Error?`${e.message}\n${e.stack}`:e?e.toString():"null"}function b(e,t){const n=h[t];if(n)return n.findFunction(e);throw new Error(`JS object instance with ID ${t} does not exist (has it been disposed?).`)}function _(e){delete h[e]}e.findJSFunction=b,e.disposeJSObjectReferenceById=_;class E{constructor(e,t){this._id=e,this._callDispatcher=t}invokeMethod(e,...t){return this._callDispatcher.invokeDotNetMethod(null,e,this._id,t)}invokeMethodAsync(e,...t){return this._callDispatcher.invokeDotNetMethodAsync(null,e,this._id,t)}dispose(){this._callDispatcher.invokeDotNetMethodAsync(null,"__Dispose",this._id,null).catch((e=>console.error(e)))}serializeAsArg(){return{[o]:this._id}}}e.DotNetObject=E,p((function(e,t){if(t&&"object"==typeof t){if(t.hasOwnProperty(o))return new E(t[o],c);if(t.hasOwnProperty(n)){const e=t[n],o=h[e];if(o)return o.getWrappedObject();throw new Error(`JS object instance with Id '${e}' does not exist. It may have been disposed.`)}if(t.hasOwnProperty(r)){const e=t[r],n=c.processByteArray(e);if(void 0===n)throw new Error(`Byte array index '${e}' does not exist.`);return n}if(t.hasOwnProperty(s)){const e=t[s],n=c.getDotNetStreamPromise(e);return new S(n)}}return t}));class S{constructor(e){this._streamPromise=e}stream(){return this._streamPromise}async arrayBuffer(){return new Response(await this.stream()).arrayBuffer()}}class C{constructor(){this.streamPromise=new Promise(((e,t)=>{this.resolve=e,this.reject=t}))}}function I(e,t){switch(t){case d.Default:return e;case d.JSObjectReference:return f(e);case d.JSStreamReference:return g(e);case d.JSVoidResult:return null;default:throw new Error(`Invalid JS call result type '${t}'.`)}}let k=0;function T(e,t){k=0,c=e;const n=JSON.stringify(t,D);return c=void 0,n}function D(e,t){if(t instanceof E)return t.serializeAsArg();if(t instanceof Uint8Array){c.getDotNetCallDispatcher().sendByteArray(k,t);const e={[r]:k};return k++,e}return t}}(e||(e={})),function(e){e[e.prependFrame=1]="prependFrame",e[e.removeFrame=2]="removeFrame",e[e.setAttribute=3]="setAttribute",e[e.removeAttribute=4]="removeAttribute",e[e.updateText=5]="updateText",e[e.stepIn=6]="stepIn",e[e.stepOut=7]="stepOut",e[e.updateMarkup=8]="updateMarkup",e[e.permutationListEntry=9]="permutationListEntry",e[e.permutationListEnd=10]="permutationListEnd"}(t||(t={})),function(e){e[e.element=1]="element",e[e.text=2]="text",e[e.attribute=3]="attribute",e[e.component=4]="component",e[e.region=5]="region",e[e.elementReferenceCapture=6]="elementReferenceCapture",e[e.markup=8]="markup",e[e.namedEvent=10]="namedEvent"}(o||(o={}));class r{constructor(e,t){this.componentId=e,this.fieldValue=t}static fromEvent(e,t){const n=t.target;if(n instanceof Element){const t=function(e){return e instanceof HTMLInputElement?e.type&&"checkbox"===e.type.toLowerCase()?{value:e.checked}:{value:e.value}:e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement?{value:e.value}:null}(n);if(t)return new r(e,t.value)}return null}}const s=new Map,i=new Map,a=[];function c(e){return s.get(e)}function l(e){const t=s.get(e);return(null==t?void 0:t.browserEventName)||e}function h(e,t){e.forEach((e=>s.set(e,t)))}function d(e){const t=[];for(let n=0;ne.selected)).map((e=>e.value))}}{const e=function(e){return!!e&&"INPUT"===e.tagName&&"checkbox"===e.getAttribute("type")}(t);return{value:e?!!t.checked:t.value}}}}),h(["copy","cut","paste"],{createEventArgs:e=>({type:e.type})}),h(["drag","dragend","dragenter","dragleave","dragover","dragstart","drop"],{createEventArgs:e=>{return{...u(t=e),dataTransfer:t.dataTransfer?{dropEffect:t.dataTransfer.dropEffect,effectAllowed:t.dataTransfer.effectAllowed,files:Array.from(t.dataTransfer.files).map((e=>e.name)),items:Array.from(t.dataTransfer.items).map((e=>({kind:e.kind,type:e.type}))),types:t.dataTransfer.types}:null};var t}}),h(["focus","blur","focusin","focusout"],{createEventArgs:e=>({type:e.type})}),h(["keydown","keyup","keypress"],{createEventArgs:e=>{return{key:(t=e).key,code:t.code,location:t.location,repeat:t.repeat,ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),h(["contextmenu","click","mouseover","mouseout","mousemove","mousedown","mouseup","mouseleave","mouseenter","dblclick"],{createEventArgs:e=>u(e)}),h(["error"],{createEventArgs:e=>{return{message:(t=e).message,filename:t.filename,lineno:t.lineno,colno:t.colno,type:t.type};var t}}),h(["loadstart","timeout","abort","load","loadend","progress"],{createEventArgs:e=>{return{lengthComputable:(t=e).lengthComputable,loaded:t.loaded,total:t.total,type:t.type};var t}}),h(["touchcancel","touchend","touchmove","touchenter","touchleave","touchstart"],{createEventArgs:e=>{return{detail:(t=e).detail,touches:d(t.touches),targetTouches:d(t.targetTouches),changedTouches:d(t.changedTouches),ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),h(["gotpointercapture","lostpointercapture","pointercancel","pointerdown","pointerenter","pointerleave","pointermove","pointerout","pointerover","pointerup"],{createEventArgs:e=>{return{...u(t=e),pointerId:t.pointerId,width:t.width,height:t.height,pressure:t.pressure,tiltX:t.tiltX,tiltY:t.tiltY,pointerType:t.pointerType,isPrimary:t.isPrimary};var t}}),h(["wheel","mousewheel"],{createEventArgs:e=>{return{...u(t=e),deltaX:t.deltaX,deltaY:t.deltaY,deltaZ:t.deltaZ,deltaMode:t.deltaMode};var t}}),h(["cancel","close","toggle"],{createEventArgs:()=>({})});const p=["date","datetime-local","month","time","week"],f=new Map;let g,m,v=0;const y={async add(e,t,n){if(!n)throw new Error("initialParameters must be an object, even if empty.");const o="__bl-dynamic-root:"+(++v).toString();f.set(o,e);const r=await E().invokeMethodAsync("AddRootComponent",t,o),s=new _(r,m[t]);return await s.setParameters(n),s}};function w(e){const t=f.get(e);if(t)return f.delete(e),t}class b{invoke(e){return this._callback(e)}setCallback(t){this._selfJSObjectReference||(this._selfJSObjectReference=e.createJSObjectReference(this)),this._callback=t}getJSObjectReference(){return this._selfJSObjectReference}dispose(){this._selfJSObjectReference&&e.disposeJSObjectReference(this._selfJSObjectReference)}}class _{constructor(e,t){this._jsEventCallbackWrappers=new Map,this._componentId=e;for(const e of t)"eventcallback"===e.type&&this._jsEventCallbackWrappers.set(e.name.toLowerCase(),new b)}setParameters(e){const t={},n=Object.entries(e||{}),o=n.length;for(const[e,o]of n){const n=this._jsEventCallbackWrappers.get(e.toLowerCase());n&&o?(n.setCallback(o),t[e]=n.getJSObjectReference()):t[e]=o}return E().invokeMethodAsync("SetRootComponentParameters",this._componentId,o,t)}async dispose(){if(null!==this._componentId){await E().invokeMethodAsync("RemoveRootComponent",this._componentId),this._componentId=null;for(const e of this._jsEventCallbackWrappers.values())e.dispose()}}}function E(){if(!g)throw new Error("Dynamic root components have not been enabled in this application.");return g}const S=new Map,C=[];let I;const k=new Promise((e=>{I=e}));function T(e,t,n){return R(e,t.eventHandlerId,(()=>D(e).invokeMethodAsync("DispatchEventAsync",t,n)))}function D(e){const t=S.get(e);if(!t)throw new Error(`No interop methods are registered for renderer ${e}`);return t}let R=(e,t,n)=>n();const x=L(["abort","blur","cancel","canplay","canplaythrough","change","close","cuechange","durationchange","emptied","ended","error","focus","load","loadeddata","loadedmetadata","loadend","loadstart","mouseenter","mouseleave","pointerenter","pointerleave","pause","play","playing","progress","ratechange","reset","scroll","seeked","seeking","stalled","submit","suspend","timeupdate","toggle","unload","volumechange","waiting","DOMNodeInsertedIntoDocument","DOMNodeRemovedFromDocument"]),A={submit:!0},N=L(["click","dblclick","mousedown","mousemove","mouseup"]);class P{constructor(e){this.browserRendererId=e,this.afterClickCallbacks=[];const t=++P.nextEventDelegatorId;this.eventsCollectionKey=`_blazorEvents_${t}`,this.eventInfoStore=new M(this.onGlobalEvent.bind(this))}setListener(e,t,n,o){const r=this.getEventHandlerInfosForElement(e,!0),s=r.getHandler(t);if(s)this.eventInfoStore.update(s.eventHandlerId,n);else{const s={element:e,eventName:t,eventHandlerId:n,renderingComponentId:o};this.eventInfoStore.add(s),r.setHandler(t,s)}}getHandler(e){return this.eventInfoStore.get(e)}removeListener(e){const t=this.eventInfoStore.remove(e);if(t){const e=t.element,n=this.getEventHandlerInfosForElement(e,!1);n&&n.removeHandler(t.eventName)}}notifyAfterClick(e){this.afterClickCallbacks.push(e),this.eventInfoStore.addGlobalListener("click")}setStopPropagation(e,t,n){this.getEventHandlerInfosForElement(e,!0).stopPropagation(t,n)}setPreventDefault(e,t,n){this.getEventHandlerInfosForElement(e,!0).preventDefault(t,n)}onGlobalEvent(e){if(!(e.target instanceof Element))return;this.dispatchGlobalEventToAllElements(e.type,e);const t=(n=e.type,i.get(n));var n;t&&t.forEach((t=>this.dispatchGlobalEventToAllElements(t,e))),"click"===e.type&&this.afterClickCallbacks.forEach((t=>t(e)))}dispatchGlobalEventToAllElements(e,t){const n=t.composedPath();let o=n.shift(),s=null,i=!1;const a=Object.prototype.hasOwnProperty.call(x,e);let l=!1;for(;o;){const u=o,p=this.getEventHandlerInfosForElement(u,!1);if(p){const n=p.getHandler(e);if(n&&(h=u,d=t.type,!((h instanceof HTMLButtonElement||h instanceof HTMLInputElement||h instanceof HTMLTextAreaElement||h instanceof HTMLSelectElement)&&Object.prototype.hasOwnProperty.call(N,d)&&h.disabled))){if(!i){const n=c(e);s=(null==n?void 0:n.createEventArgs)?n.createEventArgs(t):{},i=!0}Object.prototype.hasOwnProperty.call(A,t.type)&&t.preventDefault(),T(this.browserRendererId,{eventHandlerId:n.eventHandlerId,eventName:e,eventFieldInfo:r.fromEvent(n.renderingComponentId,t)},s)}p.stopPropagation(e)&&(l=!0),p.preventDefault(e)&&t.preventDefault()}o=a||l?void 0:n.shift()}var h,d}getEventHandlerInfosForElement(e,t){return Object.prototype.hasOwnProperty.call(e,this.eventsCollectionKey)?e[this.eventsCollectionKey]:t?e[this.eventsCollectionKey]=new U:null}}P.nextEventDelegatorId=0;class M{constructor(e){this.globalListener=e,this.infosByEventHandlerId={},this.countByEventName={},a.push(this.handleEventNameAliasAdded.bind(this))}add(e){if(this.infosByEventHandlerId[e.eventHandlerId])throw new Error(`Event ${e.eventHandlerId} is already tracked`);this.infosByEventHandlerId[e.eventHandlerId]=e,this.addGlobalListener(e.eventName)}get(e){return this.infosByEventHandlerId[e]}addGlobalListener(e){if(e=l(e),Object.prototype.hasOwnProperty.call(this.countByEventName,e))this.countByEventName[e]++;else{this.countByEventName[e]=1;const t=Object.prototype.hasOwnProperty.call(x,e);document.addEventListener(e,this.globalListener,t)}}update(e,t){if(Object.prototype.hasOwnProperty.call(this.infosByEventHandlerId,t))throw new Error(`Event ${t} is already tracked`);const n=this.infosByEventHandlerId[e];delete this.infosByEventHandlerId[e],n.eventHandlerId=t,this.infosByEventHandlerId[t]=n}remove(e){const t=this.infosByEventHandlerId[e];if(t){delete this.infosByEventHandlerId[e];const n=l(t.eventName);0==--this.countByEventName[n]&&(delete this.countByEventName[n],document.removeEventListener(n,this.globalListener))}return t}handleEventNameAliasAdded(e,t){if(Object.prototype.hasOwnProperty.call(this.countByEventName,e)){const n=this.countByEventName[e];delete this.countByEventName[e],document.removeEventListener(e,this.globalListener),this.addGlobalListener(t),this.countByEventName[t]+=n-1}}}class U{constructor(){this.handlers={},this.preventDefaultFlags=null,this.stopPropagationFlags=null}getHandler(e){return Object.prototype.hasOwnProperty.call(this.handlers,e)?this.handlers[e]:null}setHandler(e,t){this.handlers[e]=t}removeHandler(e){delete this.handlers[e]}preventDefault(e,t){return void 0!==t&&(this.preventDefaultFlags=this.preventDefaultFlags||{},this.preventDefaultFlags[e]=t),!!this.preventDefaultFlags&&this.preventDefaultFlags[e]}stopPropagation(e,t){return void 0!==t&&(this.stopPropagationFlags=this.stopPropagationFlags||{},this.stopPropagationFlags[e]=t),!!this.stopPropagationFlags&&this.stopPropagationFlags[e]}}function L(e){const t={};return e.forEach((e=>{t[e]=!0})),t}const B=Symbol(),F=Symbol(),O=Symbol();function $(e){const{start:t,end:n}=e,o=t[O];if(o){if(o!==e)throw new Error("The start component comment was already associated with another component descriptor.");return t}const r=t.parentNode;if(!r)throw new Error(`Comment not connected to the DOM ${t.textContent}`);const s=H(r,!0),i=G(s);t[F]=s,t[O]=e;const a=H(t);if(n){const e=G(a),o=Array.prototype.indexOf.call(i,a)+1;let r=null;for(;r!==n;){const n=i.splice(o,1)[0];if(!n)throw new Error("Could not find the end component comment in the parent logical node list");n[F]=t,e.push(n),r=n}}return a}function H(e,t){if(B in e)return e;const n=[];if(e.childNodes.length>0){if(!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");e.childNodes.forEach((t=>{const o=H(t,!0);o[F]=e,n.push(o)}))}return e[B]=n,e}function j(e){const t=G(e);for(;t.length;)z(e,0)}function W(e,t){const n=document.createComment("!");return q(n,e,t),n}function q(e,t,n){const o=e;let r=e;if(Q(e)){const t=ne(o);if(t!==e){const n=new Range;n.setStartBefore(e),n.setEndAfter(t),r=n.extractContents()}}const s=J(o);if(s){const e=G(s),t=Array.prototype.indexOf.call(e,o);e.splice(t,1),delete o[F]}const i=G(t);if(n0;)z(n,0)}const o=n;o.parentNode.removeChild(o)}function J(e){return e[F]||null}function V(e,t){return G(e)[t]}function K(e){return e[O]||null}function X(e){const t=ee(e);return"http://www.w3.org/2000/svg"===t.namespaceURI&&"foreignObject"!==t.tagName}function G(e){return e[B]}function Y(e){const t=G(J(e));return t[Array.prototype.indexOf.call(t,e)+1]||null}function Q(e){return B in e}function Z(e,t){const n=G(e);t.forEach((e=>{e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=ne(e.moveRangeStart)})),t.forEach((t=>{const o=document.createComment("marker");t.moveToBeforeMarker=o;const r=n[t.toSiblingIndex+1];r?r.parentNode.insertBefore(o,r):te(o,e)})),t.forEach((e=>{const t=e.moveToBeforeMarker,n=t.parentNode,o=e.moveRangeStart,r=e.moveRangeEnd;let s=o;for(;s;){const e=s.nextSibling;if(n.insertBefore(s,t),s===r)break;s=e}n.removeChild(t)})),t.forEach((e=>{n[e.toSiblingIndex]=e.moveRangeStart}))}function ee(e){if(e instanceof Element||e instanceof DocumentFragment)return e;if(e instanceof Comment)return e.parentNode;throw new Error("Not a valid logical element")}function te(e,t){if(t instanceof Element||t instanceof DocumentFragment)t.appendChild(e);else{if(!(t instanceof Comment))throw new Error(`Cannot append node because the parent is not a valid logical element. Parent: ${t}`);{const n=Y(t);n?n.parentNode.insertBefore(e,n):te(e,J(t))}}}function ne(e){if(e instanceof Element||e instanceof DocumentFragment)return e;const t=Y(e);if(t)return t.previousSibling;{const t=J(e);return t instanceof Element||t instanceof DocumentFragment?t.lastChild:ne(t)}}function oe(e){return`_bl_${e}`}const re="__internalId";e.attachReviver(((e,t)=>t&&"object"==typeof t&&Object.prototype.hasOwnProperty.call(t,re)&&"string"==typeof t[re]?function(e){const t=`[${oe(e)}]`;return document.querySelector(t)}(t[re]):t));const se="_blazorDeferredValue";function ie(e){e instanceof HTMLOptionElement?he(e):se in e&&le(e,e[se])}function ae(e){return"select-multiple"===e.type}function ce(e,t){e.value=t||""}function le(e,t){e instanceof HTMLSelectElement?ae(e)?function(e,t){t||(t=[]);for(let n=0;n{Me()&&De(e,(e=>{Je(e,!0,!1)}))}))}getRootComponentCount(){return this.rootComponentIds.size}attachRootComponentToLogicalElement(e,t,n){if(ve(t))throw new Error(`Root component '${e}' could not be attached because its target element is already associated with a root component`);me(t,!0),this.attachComponentToElement(e,t),this.rootComponentIds.add(e),n||(pe[e]=t)}updateComponent(e,t,n,o){var r;const s=this.childComponentLocations[t];if(!s)throw new Error(`No element is currently associated with component ${t}`);const i=pe[t];i&&(delete pe[t],j(i),i instanceof Comment&&(i.textContent="!"));const a=null===(r=ee(s))||void 0===r?void 0:r.getRootNode(),c=a&&a.activeElement;this.applyEdits(e,t,s,0,n,o),c instanceof HTMLElement&&a&&a.activeElement!==c&&c.focus()}disposeComponent(e){if(this.rootComponentIds.delete(e)){const t=this.childComponentLocations[e];me(t,!1),j(t)}delete this.childComponentLocations[e]}disposeEventHandler(e){this.eventDelegator.removeListener(e)}attachComponentToElement(e,t){this.childComponentLocations[e]=t}applyEdits(e,n,o,r,s,i){let a,c=0,l=r;const h=e.arrayBuilderSegmentReader,d=e.editReader,u=e.frameReader,p=h.values(s),f=h.offset(s),g=f+h.count(s);for(let s=f;sdocument.baseURI,getLocationHref:()=>location.href,scrollToElement:qe};function qe(e){const t=document.getElementById(e);return!!t&&(t.scrollIntoView(),!0)}function ze(e,t,n=!1){const o=Ne(e);!t.forceLoad&&Re(o)?Ze()?Je(o,!1,t.replaceHistoryEntry,t.historyEntryState,n):Ae(o,t.replaceHistoryEntry):function(e,t){if(location.href===e){const t=e+"?";history.replaceState(null,"",t),location.replace(e)}else t?location.replace(e):location.href=e}(e,t.replaceHistoryEntry)}async function Je(e,t,n,o=void 0,r=!1){if(Xe(),function(e){const t=e.indexOf("#");return t>-1&&location.href.replace(location.hash,"")===e.substring(0,t)}(e))!function(e,t,n){Ve(e,t,n);const o=e.indexOf("#");o!==e.length-1&&qe(e.substring(o+1))}(e,n,o);else{if(!r&&Le&&!await Ge(e,o,t))return;_e=!0,Ve(e,n,o),await Ye(t)}}function Ve(e,t,n=void 0){t?history.replaceState({userState:n,_index:Be},"",e):(Be++,history.pushState({userState:n,_index:Be},"",e))}function Ke(e){return new Promise((t=>{const n=He;He=()=>{He=n,t()},history.go(e)}))}function Xe(){je&&(je(!1),je=null)}function Ge(e,t,n){return new Promise((o=>{Xe(),$e?(Fe++,je=o,$e(Fe,e,t,n)):o(!1)}))}async function Ye(e){var t;Oe&&await Oe(location.href,null===(t=history.state)||void 0===t?void 0:t.userState,e)}async function Qe(e){var t,n;He&&Ze()&&await He(e),Be=null!==(n=null===(t=history.state)||void 0===t?void 0:t._index)&&void 0!==n?n:0}function Ze(){return Me()||!xe()}const et={focus:function(e,t){if(e instanceof HTMLElement)e.focus({preventScroll:t});else{if(!(e instanceof SVGElement))throw new Error("Unable to focus an invalid element.");if(!e.hasAttribute("tabindex"))throw new Error("Unable to focus an SVG element that does not have a tabindex.");e.focus({preventScroll:t})}},focusBySelector:function(e,t){const n=document.querySelector(e);n&&(n.hasAttribute("tabindex")||(n.tabIndex=-1),n.focus({preventScroll:!0}))}},tt={init:function(e,t,n,o=50){const r=ot(t);(r||document.documentElement).style.overflowAnchor="none";const s=document.createRange();u(n.parentElement)&&(t.style.display="table-row",n.style.display="table-row");const i=new IntersectionObserver((function(o){o.forEach((o=>{var r;if(!o.isIntersecting)return;s.setStartAfter(t),s.setEndBefore(n);const i=s.getBoundingClientRect().height,a=null===(r=o.rootBounds)||void 0===r?void 0:r.height;o.target===t?e.invokeMethodAsync("OnSpacerBeforeVisible",o.intersectionRect.top-o.boundingClientRect.top,i,a):o.target===n&&n.offsetHeight>0&&e.invokeMethodAsync("OnSpacerAfterVisible",o.boundingClientRect.bottom-o.intersectionRect.bottom,i,a)}))}),{root:r,rootMargin:`${o}px`});i.observe(t),i.observe(n);const a=d(t),c=d(n),{observersByDotNetObjectId:l,id:h}=rt(e);function d(e){const t={attributes:!0},n=new MutationObserver(((n,o)=>{u(e.parentElement)&&(o.disconnect(),e.style.display="table-row",o.observe(e,t)),i.unobserve(e),i.observe(e)}));return n.observe(e,t),n}function u(e){return null!==e&&(e instanceof HTMLTableElement&&""===e.style.display||"table"===e.style.display||e instanceof HTMLTableSectionElement&&""===e.style.display||"table-row-group"===e.style.display)}l[h]={intersectionObserver:i,mutationObserverBefore:a,mutationObserverAfter:c}},dispose:function(e){const{observersByDotNetObjectId:t,id:n}=rt(e),o=t[n];o&&(o.intersectionObserver.disconnect(),o.mutationObserverBefore.disconnect(),o.mutationObserverAfter.disconnect(),e.dispose(),delete t[n])}},nt=Symbol();function ot(e){return e&&e!==document.body&&e!==document.documentElement?"visible"!==getComputedStyle(e).overflowY?e:ot(e.parentElement):null}function rt(e){var t;const n=e._callDispatcher,o=e._id;return null!==(t=n[nt])&&void 0!==t||(n[nt]={}),{observersByDotNetObjectId:n[nt],id:o}}const st={getAndRemoveExistingTitle:function(){var e;const t=document.head?document.head.getElementsByTagName("title"):[];if(0===t.length)return null;let n=null;for(let o=t.length-1;o>=0;o--){const r=t[o],s=r.previousSibling;s instanceof Comment&&null!==J(s)||(null===n&&(n=r.textContent),null===(e=r.parentNode)||void 0===e||e.removeChild(r))}return n}},it={init:function(e,t){t._blazorInputFileNextFileId=0,t.addEventListener("click",(function(){t.value=""})),t.addEventListener("change",(function(){t._blazorFilesById={};const n=Array.prototype.map.call(t.files,(function(e){const n={id:++t._blazorInputFileNextFileId,lastModified:new Date(e.lastModified).toISOString(),name:e.name,size:e.size,contentType:e.type,readPromise:void 0,arrayBuffer:void 0,blob:e};return t._blazorFilesById[n.id]=n,n}));e.invokeMethodAsync("NotifyChange",n)}))},toImageFile:async function(e,t,n,o,r){const s=at(e,t),i=await new Promise((function(e){const t=new Image;t.onload=function(){URL.revokeObjectURL(t.src),e(t)},t.onerror=function(){t.onerror=null,URL.revokeObjectURL(t.src)},t.src=URL.createObjectURL(s.blob)})),a=await new Promise((function(e){var t;const s=Math.min(1,o/i.width),a=Math.min(1,r/i.height),c=Math.min(s,a),l=document.createElement("canvas");l.width=Math.round(i.width*c),l.height=Math.round(i.height*c),null===(t=l.getContext("2d"))||void 0===t||t.drawImage(i,0,0,l.width,l.height),l.toBlob(e,n)})),c={id:++e._blazorInputFileNextFileId,lastModified:s.lastModified,name:s.name,size:(null==a?void 0:a.size)||0,contentType:n,blob:a||s.blob};return e._blazorFilesById[c.id]=c,c},readFileData:async function(e,t){return at(e,t).blob}};function at(e,t){const n=e._blazorFilesById[t];if(!n)throw new Error(`There is no file with ID ${t}. The file list may have changed. See https://aka.ms/aspnet/blazor-input-file-multiple-selections.`);return n}const ct=new Set,lt={enableNavigationPrompt:function(e){0===ct.size&&window.addEventListener("beforeunload",ht),ct.add(e)},disableNavigationPrompt:function(e){ct.delete(e),0===ct.size&&window.removeEventListener("beforeunload",ht)}};function ht(e){e.preventDefault(),e.returnValue=!0}async function dt(e,t,n){return e instanceof Blob?await async function(e,t,n){const o=e.slice(t,t+n),r=await o.arrayBuffer();return new Uint8Array(r)}(e,t,n):function(e,t,n){return new Uint8Array(e.buffer,e.byteOffset+t,n)}(e,t,n)}const ut=new Map,pt={navigateTo:function(e,t,n=!1){ze(e,t instanceof Object?t:{forceLoad:t,replaceHistoryEntry:n})},registerCustomEventType:function(e,t){if(!t)throw new Error("The options parameter is required.");if(s.has(e))throw new Error(`The event '${e}' is already registered.`);if(t.browserEventName){const n=i.get(t.browserEventName);n?n.push(e):i.set(t.browserEventName,[e]),a.forEach((n=>n(e,t.browserEventName)))}s.set(e,t)},rootComponents:y,runtime:{},_internal:{navigationManager:We,domWrapper:et,Virtualize:tt,PageTitle:st,InputFile:it,NavigationLock:lt,getJSDataStreamChunk:dt,attachWebRendererInterop:function(t,n,o,r){if(S.has(t))throw new Error(`Interop methods are already registered for renderer ${t}`);S.set(t,n),Object.keys(o).length>0&&function(t,n,o){if(g)throw new Error("Dynamic root components have already been enabled.");g=t,m=n;for(const[t,r]of Object.entries(o)){const o=e.findJSFunction(t,0);for(const e of r)o(e,n[e])}}(D(t),o,r),I(),function(e){for(const t of C)t(e)}(t)}}};var ft;window.Blazor=pt,function(e){e[e.Trace=0]="Trace",e[e.Debug=1]="Debug",e[e.Information=2]="Information",e[e.Warning=3]="Warning",e[e.Error=4]="Error",e[e.Critical=5]="Critical",e[e.None=6]="None"}(ft||(ft={}));class gt{log(e,t){}}gt.instance=new gt;class mt{constructor(e){this.minLevel=e}log(e,t){if(e>=this.minLevel){const n=`[${(new Date).toISOString()}] ${ft[e]}: ${t}`;switch(e){case ft.Critical:case ft.Error:console.error(n);break;case ft.Warning:console.warn(n);break;case ft.Information:console.info(n);break;default:console.log(n)}}}}function vt(e,t){switch(t){case"webassembly":return Et(e,"webassembly");case"server":return function(e){return Et(e,"server").sort(((e,t)=>e.sequence-t.sequence))}(e);case"auto":return Et(e,"auto")}}const yt=/^\s*Blazor-Server-Component-State:(?[a-zA-Z0-9+/=]+)$/,wt=/^\s*Blazor-WebAssembly-Component-State:(?[a-zA-Z0-9+/=]+)$/;function bt(e){return _t(e,yt)}function _t(e,t){var n;if(e.nodeType===Node.COMMENT_NODE){const o=e.textContent||"",r=t.exec(o),s=r&&r.groups&&r.groups.state;return s&&(null===(n=e.parentNode)||void 0===n||n.removeChild(e)),s}if(!e.hasChildNodes())return;const o=e.childNodes;for(let e=0;e.*)$/);function Ct(e,t){const n=e.currentElement;var o,r,s;if(n&&n.nodeType===Node.COMMENT_NODE&&n.textContent){const i=St.exec(n.textContent),a=i&&i.groups&&i.groups.descriptor;if(!a)return;!function(e){if(e.parentNode instanceof Document)throw new Error("Root components cannot be marked as interactive. The element must be rendered statically so that scripts are not evaluated multiple times.")}(n);try{const i=function(e){const t=JSON.parse(e),{type:n}=t;if("server"!==n&&"webassembly"!==n&&"auto"!==n)throw new Error(`Invalid component type '${n}'.`);return t}(a),c=function(e,t,n){const{prerenderId:o}=e;if(o){for(;n.next()&&n.currentElement;){const e=n.currentElement;if(e.nodeType!==Node.COMMENT_NODE)continue;if(!e.textContent)continue;const t=St.exec(e.textContent),r=t&&t[1];if(r)return Dt(r,o),e}throw new Error(`Could not find an end component comment for '${t}'.`)}}(i,n,e);if(t!==i.type)return;switch(i.type){case"webassembly":return r=n,s=c,Tt(o=i),{...o,uniqueId:It++,start:r,end:s};case"server":return function(e,t,n){return kt(e),{...e,uniqueId:It++,start:t,end:n}}(i,n,c);case"auto":return function(e,t,n){return kt(e),Tt(e),{...e,uniqueId:It++,start:t,end:n}}(i,n,c)}}catch(e){throw new Error(`Found malformed component comment at ${n.textContent}`)}}}let It=0;function kt(e){const{descriptor:t,sequence:n}=e;if(!t)throw new Error("descriptor must be defined when using a descriptor.");if(void 0===n)throw new Error("sequence must be defined when using a descriptor.");if(!Number.isInteger(n))throw new Error(`Error parsing the sequence '${n}' for component '${JSON.stringify(e)}'`)}function Tt(e){const{assembly:t,typeName:n}=e;if(!t)throw new Error("assembly must be defined when using a descriptor.");if(!n)throw new Error("typeName must be defined when using a descriptor.");e.parameterDefinitions=e.parameterDefinitions&&atob(e.parameterDefinitions),e.parameterValues=e.parameterValues&&atob(e.parameterValues)}function Dt(e,t){const n=JSON.parse(e);if(1!==Object.keys(n).length)throw new Error(`Invalid end of component comment: '${e}'`);const o=n.prerenderId;if(!o)throw new Error(`End of component comment must have a value for the prerendered property: '${e}'`);if(o!==t)throw new Error(`End of component comment prerendered property must match the start comment prerender id: '${t}', '${o}'`)}class Rt{constructor(e){this.childNodes=e,this.currentIndex=-1,this.length=e.length}next(){return this.currentIndex++,this.currentIndex{n+=`0x${e<16?"0":""}${e.toString(16)} `})),n.substr(0,n.length-1)}(e)}'`)):"string"==typeof e&&(n=`String data of length ${e.length}`,t&&(n+=`. Content: '${e}'`)),n}function Ot(e){return e&&"undefined"!=typeof ArrayBuffer&&(e instanceof ArrayBuffer||e.constructor&&"ArrayBuffer"===e.constructor.name)}async function $t(e,t,n,o,r,s){const i={},[a,c]=Wt();i[a]=c,e.log(Pt.Trace,`(${t} transport) sending data. ${Ft(r,s.logMessageContent)}.`);const l=Ot(r)?"arraybuffer":"text",h=await n.post(o,{content:r,headers:{...i,...s.headers},responseType:l,timeout:s.timeout,withCredentials:s.withCredentials});e.log(Pt.Trace,`(${t} transport) request complete. Response status: ${h.statusCode}.`)}class Ht{constructor(e,t){this._subject=e,this._observer=t}dispose(){const e=this._subject.observers.indexOf(this._observer);e>-1&&this._subject.observers.splice(e,1),0===this._subject.observers.length&&this._subject.cancelCallback&&this._subject.cancelCallback().catch((e=>{}))}}class jt{constructor(e){this._minLevel=e,this.out=console}log(e,t){if(e>=this._minLevel){const n=`[${(new Date).toISOString()}] ${Pt[e]}: ${t}`;switch(e){case Pt.Critical:case Pt.Error:this.out.error(n);break;case Pt.Warning:this.out.warn(n);break;case Pt.Information:this.out.info(n);break;default:this.out.log(n)}}}}function Wt(){let e="X-SignalR-User-Agent";return Bt.isNode&&(e="User-Agent"),[e,qt(Ut,zt(),Bt.isNode?"NodeJS":"Browser",Jt())]}function qt(e,t,n,o){let r="Microsoft SignalR/";const s=e.split(".");return r+=`${s[0]}.${s[1]}`,r+=` (${e}; `,r+=t&&""!==t?`${t}; `:"Unknown OS; ",r+=`${n}`,r+=o?`; ${o}`:"; Unknown Runtime Version",r+=")",r}function zt(){if(!Bt.isNode)return"";switch(process.platform){case"win32":return"Windows NT";case"darwin":return"macOS";case"linux":return"Linux";default:return process.platform}}function Jt(){if(Bt.isNode)return process.versions.node}function Vt(e){return e.stack?e.stack:e.message?e.message:`${e}`}class Kt{writeHandshakeRequest(e){return Nt.write(JSON.stringify(e))}parseHandshakeResponse(e){let t,n;if(Ot(e)){const o=new Uint8Array(e),r=o.indexOf(Nt.RecordSeparatorCode);if(-1===r)throw new Error("Message is incomplete.");const s=r+1;t=String.fromCharCode.apply(null,Array.prototype.slice.call(o.slice(0,s))),n=o.byteLength>s?o.slice(s).buffer:null}else{const o=e,r=o.indexOf(Nt.RecordSeparator);if(-1===r)throw new Error("Message is incomplete.");const s=r+1;t=o.substring(0,s),n=o.length>s?o.substring(s):null}const o=Nt.parse(t),r=JSON.parse(o[0]);if(r.type)throw new Error("Expected a handshake response from the server.");return[n,r]}}class Xt extends Error{constructor(e,t){const n=new.target.prototype;super(`${e}: Status code '${t}'`),this.statusCode=t,this.__proto__=n}}class Gt extends Error{constructor(e="A timeout occurred."){const t=new.target.prototype;super(e),this.__proto__=t}}class Yt extends Error{constructor(e="An abort occurred."){const t=new.target.prototype;super(e),this.__proto__=t}}class Qt extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="UnsupportedTransportError",this.__proto__=n}}class Zt extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="DisabledTransportError",this.__proto__=n}}class en extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="FailedToStartTransportError",this.__proto__=n}}class tn extends Error{constructor(e){const t=new.target.prototype;super(e),this.errorType="FailedToNegotiateWithServerError",this.__proto__=t}}class nn extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.innerErrors=t,this.__proto__=n}}var on,rn;!function(e){e[e.Invocation=1]="Invocation",e[e.StreamItem=2]="StreamItem",e[e.Completion=3]="Completion",e[e.StreamInvocation=4]="StreamInvocation",e[e.CancelInvocation=5]="CancelInvocation",e[e.Ping=6]="Ping",e[e.Close=7]="Close",e[e.Ack=8]="Ack",e[e.Sequence=9]="Sequence"}(on||(on={}));class sn{constructor(){this.observers=[]}next(e){for(const t of this.observers)t.next(e)}error(e){for(const t of this.observers)t.error&&t.error(e)}complete(){for(const e of this.observers)e.complete&&e.complete()}subscribe(e){return this.observers.push(e),new Ht(this,e)}}class an{constructor(e,t,n){this._bufferSize=1e5,this._messages=[],this._totalMessageCount=0,this._waitForSequenceMessage=!1,this._nextReceivingSequenceId=1,this._latestReceivedSequenceId=0,this._bufferedByteCount=0,this._reconnectInProgress=!1,this._protocol=e,this._connection=t,this._bufferSize=n}async _send(e){const t=this._protocol.writeMessage(e);let n=Promise.resolve();if(this._isInvocationMessage(e)){this._totalMessageCount++;let e=()=>{},o=()=>{};Ot(t)?this._bufferedByteCount+=t.byteLength:this._bufferedByteCount+=t.length,this._bufferedByteCount>=this._bufferSize&&(n=new Promise(((t,n)=>{e=t,o=n}))),this._messages.push(new cn(t,this._totalMessageCount,e,o))}try{this._reconnectInProgress||await this._connection.send(t)}catch{this._disconnected()}await n}_ack(e){let t=-1;for(let n=0;nthis._nextReceivingSequenceId?this._connection.stop(new Error("Sequence ID greater than amount of messages we've received.")):this._nextReceivingSequenceId=e.sequenceId}_disconnected(){this._reconnectInProgress=!0,this._waitForSequenceMessage=!0}async _resend(){const e=0!==this._messages.length?this._messages[0]._id:this._totalMessageCount+1;await this._connection.send(this._protocol.writeMessage({type:on.Sequence,sequenceId:e}));const t=this._messages;for(const e of t)await this._connection.send(e._message);this._reconnectInProgress=!1}_dispose(e){null!=e||(e=new Error("Unable to reconnect to server."));for(const t of this._messages)t._rejector(e)}_isInvocationMessage(e){switch(e.type){case on.Invocation:case on.StreamItem:case on.Completion:case on.StreamInvocation:case on.CancelInvocation:return!0;case on.Close:case on.Sequence:case on.Ping:case on.Ack:return!1}}_ackTimer(){void 0===this._ackTimerHandle&&(this._ackTimerHandle=setTimeout((async()=>{try{this._reconnectInProgress||await this._connection.send(this._protocol.writeMessage({type:on.Ack,sequenceId:this._latestReceivedSequenceId}))}catch{}clearTimeout(this._ackTimerHandle),this._ackTimerHandle=void 0}),1e3))}}class cn{constructor(e,t,n,o){this._message=e,this._id=t,this._resolver=n,this._rejector=o}}!function(e){e.Disconnected="Disconnected",e.Connecting="Connecting",e.Connected="Connected",e.Disconnecting="Disconnecting",e.Reconnecting="Reconnecting"}(rn||(rn={}));class ln{static create(e,t,n,o,r,s,i){return new ln(e,t,n,o,r,s,i)}constructor(e,t,n,o,r,s,i){this._nextKeepAlive=0,this._freezeEventListener=()=>{this._logger.log(Pt.Warning,"The page is being frozen, this will likely lead to the connection being closed and messages being lost. For more information see the docs at https://learn.microsoft.com/aspnet/core/signalr/javascript-client#bsleep")},Lt.isRequired(e,"connection"),Lt.isRequired(t,"logger"),Lt.isRequired(n,"protocol"),this.serverTimeoutInMilliseconds=null!=r?r:3e4,this.keepAliveIntervalInMilliseconds=null!=s?s:15e3,this._statefulReconnectBufferSize=null!=i?i:1e5,this._logger=t,this._protocol=n,this.connection=e,this._reconnectPolicy=o,this._handshakeProtocol=new Kt,this.connection.onreceive=e=>this._processIncomingData(e),this.connection.onclose=e=>this._connectionClosed(e),this._callbacks={},this._methods={},this._closedCallbacks=[],this._reconnectingCallbacks=[],this._reconnectedCallbacks=[],this._invocationId=0,this._receivedHandshakeResponse=!1,this._connectionState=rn.Disconnected,this._connectionStarted=!1,this._cachedPingMessage=this._protocol.writeMessage({type:on.Ping})}get state(){return this._connectionState}get connectionId(){return this.connection&&this.connection.connectionId||null}get baseUrl(){return this.connection.baseUrl||""}set baseUrl(e){if(this._connectionState!==rn.Disconnected&&this._connectionState!==rn.Reconnecting)throw new Error("The HubConnection must be in the Disconnected or Reconnecting state to change the url.");if(!e)throw new Error("The HubConnection url must be a valid url.");this.connection.baseUrl=e}start(){return this._startPromise=this._startWithStateTransitions(),this._startPromise}async _startWithStateTransitions(){if(this._connectionState!==rn.Disconnected)return Promise.reject(new Error("Cannot start a HubConnection that is not in the 'Disconnected' state."));this._connectionState=rn.Connecting,this._logger.log(Pt.Debug,"Starting HubConnection.");try{await this._startInternal(),Bt.isBrowser&&window.document.addEventListener("freeze",this._freezeEventListener),this._connectionState=rn.Connected,this._connectionStarted=!0,this._logger.log(Pt.Debug,"HubConnection connected successfully.")}catch(e){return this._connectionState=rn.Disconnected,this._logger.log(Pt.Debug,`HubConnection failed to start successfully because of error '${e}'.`),Promise.reject(e)}}async _startInternal(){this._stopDuringStartError=void 0,this._receivedHandshakeResponse=!1;const e=new Promise(((e,t)=>{this._handshakeResolver=e,this._handshakeRejecter=t}));await this.connection.start(this._protocol.transferFormat);try{let t=this._protocol.version;this.connection.features.reconnect||(t=1);const n={protocol:this._protocol.name,version:t};if(this._logger.log(Pt.Debug,"Sending handshake request."),await this._sendMessage(this._handshakeProtocol.writeHandshakeRequest(n)),this._logger.log(Pt.Information,`Using HubProtocol '${this._protocol.name}'.`),this._cleanupTimeout(),this._resetTimeoutPeriod(),this._resetKeepAliveInterval(),await e,this._stopDuringStartError)throw this._stopDuringStartError;!!this.connection.features.reconnect&&(this._messageBuffer=new an(this._protocol,this.connection,this._statefulReconnectBufferSize),this.connection.features.disconnected=this._messageBuffer._disconnected.bind(this._messageBuffer),this.connection.features.resend=()=>{if(this._messageBuffer)return this._messageBuffer._resend()}),this.connection.features.inherentKeepAlive||await this._sendMessage(this._cachedPingMessage)}catch(e){throw this._logger.log(Pt.Debug,`Hub handshake failed with error '${e}' during start(). Stopping HubConnection.`),this._cleanupTimeout(),this._cleanupPingTimer(),await this.connection.stop(e),e}}async stop(){const e=this._startPromise;this.connection.features.reconnect=!1,this._stopPromise=this._stopInternal(),await this._stopPromise;try{await e}catch(e){}}_stopInternal(e){if(this._connectionState===rn.Disconnected)return this._logger.log(Pt.Debug,`Call to HubConnection.stop(${e}) ignored because it is already in the disconnected state.`),Promise.resolve();if(this._connectionState===rn.Disconnecting)return this._logger.log(Pt.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnecting state.`),this._stopPromise;const t=this._connectionState;return this._connectionState=rn.Disconnecting,this._logger.log(Pt.Debug,"Stopping HubConnection."),this._reconnectDelayHandle?(this._logger.log(Pt.Debug,"Connection stopped during reconnect delay. Done reconnecting."),clearTimeout(this._reconnectDelayHandle),this._reconnectDelayHandle=void 0,this._completeClose(),Promise.resolve()):(t===rn.Connected&&this._sendCloseMessage(),this._cleanupTimeout(),this._cleanupPingTimer(),this._stopDuringStartError=e||new Yt("The connection was stopped before the hub handshake could complete."),this.connection.stop(e))}async _sendCloseMessage(){try{await this._sendWithProtocol(this._createCloseMessage())}catch{}}stream(e,...t){const[n,o]=this._replaceStreamingParams(t),r=this._createStreamInvocation(e,t,o);let s;const i=new sn;return i.cancelCallback=()=>{const e=this._createCancelInvocation(r.invocationId);return delete this._callbacks[r.invocationId],s.then((()=>this._sendWithProtocol(e)))},this._callbacks[r.invocationId]=(e,t)=>{t?i.error(t):e&&(e.type===on.Completion?e.error?i.error(new Error(e.error)):i.complete():i.next(e.item))},s=this._sendWithProtocol(r).catch((e=>{i.error(e),delete this._callbacks[r.invocationId]})),this._launchStreams(n,s),i}_sendMessage(e){return this._resetKeepAliveInterval(),this.connection.send(e)}_sendWithProtocol(e){return this._messageBuffer?this._messageBuffer._send(e):this._sendMessage(this._protocol.writeMessage(e))}send(e,...t){const[n,o]=this._replaceStreamingParams(t),r=this._sendWithProtocol(this._createInvocation(e,t,!0,o));return this._launchStreams(n,r),r}invoke(e,...t){const[n,o]=this._replaceStreamingParams(t),r=this._createInvocation(e,t,!1,o);return new Promise(((e,t)=>{this._callbacks[r.invocationId]=(n,o)=>{o?t(o):n&&(n.type===on.Completion?n.error?t(new Error(n.error)):e(n.result):t(new Error(`Unexpected message type: ${n.type}`)))};const o=this._sendWithProtocol(r).catch((e=>{t(e),delete this._callbacks[r.invocationId]}));this._launchStreams(n,o)}))}on(e,t){e&&t&&(e=e.toLowerCase(),this._methods[e]||(this._methods[e]=[]),-1===this._methods[e].indexOf(t)&&this._methods[e].push(t))}off(e,t){if(!e)return;e=e.toLowerCase();const n=this._methods[e];if(n)if(t){const o=n.indexOf(t);-1!==o&&(n.splice(o,1),0===n.length&&delete this._methods[e])}else delete this._methods[e]}onclose(e){e&&this._closedCallbacks.push(e)}onreconnecting(e){e&&this._reconnectingCallbacks.push(e)}onreconnected(e){e&&this._reconnectedCallbacks.push(e)}_processIncomingData(e){if(this._cleanupTimeout(),this._receivedHandshakeResponse||(e=this._processHandshakeResponse(e),this._receivedHandshakeResponse=!0),e){const t=this._protocol.parseMessages(e,this._logger);for(const e of t)if(!this._messageBuffer||this._messageBuffer._shouldProcessMessage(e))switch(e.type){case on.Invocation:this._invokeClientMethod(e);break;case on.StreamItem:case on.Completion:{const t=this._callbacks[e.invocationId];if(t){e.type===on.Completion&&delete this._callbacks[e.invocationId];try{t(e)}catch(e){this._logger.log(Pt.Error,`Stream callback threw error: ${Vt(e)}`)}}break}case on.Ping:break;case on.Close:{this._logger.log(Pt.Information,"Close message received from server.");const t=e.error?new Error("Server returned an error on close: "+e.error):void 0;!0===e.allowReconnect?this.connection.stop(t):this._stopPromise=this._stopInternal(t);break}case on.Ack:this._messageBuffer&&this._messageBuffer._ack(e);break;case on.Sequence:this._messageBuffer&&this._messageBuffer._resetSequence(e);break;default:this._logger.log(Pt.Warning,`Invalid message type: ${e.type}.`)}}this._resetTimeoutPeriod()}_processHandshakeResponse(e){let t,n;try{[n,t]=this._handshakeProtocol.parseHandshakeResponse(e)}catch(e){const t="Error parsing handshake response: "+e;this._logger.log(Pt.Error,t);const n=new Error(t);throw this._handshakeRejecter(n),n}if(t.error){const e="Server returned handshake error: "+t.error;this._logger.log(Pt.Error,e);const n=new Error(e);throw this._handshakeRejecter(n),n}return this._logger.log(Pt.Debug,"Server handshake complete."),this._handshakeResolver(),n}_resetKeepAliveInterval(){this.connection.features.inherentKeepAlive||(this._nextKeepAlive=(new Date).getTime()+this.keepAliveIntervalInMilliseconds,this._cleanupPingTimer())}_resetTimeoutPeriod(){if(!(this.connection.features&&this.connection.features.inherentKeepAlive||(this._timeoutHandle=setTimeout((()=>this.serverTimeout()),this.serverTimeoutInMilliseconds),void 0!==this._pingServerHandle))){let e=this._nextKeepAlive-(new Date).getTime();e<0&&(e=0),this._pingServerHandle=setTimeout((async()=>{if(this._connectionState===rn.Connected)try{await this._sendMessage(this._cachedPingMessage)}catch{this._cleanupPingTimer()}}),e)}}serverTimeout(){this.connection.stop(new Error("Server timeout elapsed without receiving a message from the server."))}async _invokeClientMethod(e){const t=e.target.toLowerCase(),n=this._methods[t];if(!n)return this._logger.log(Pt.Warning,`No client method with the name '${t}' found.`),void(e.invocationId&&(this._logger.log(Pt.Warning,`No result given for '${t}' method and invocation ID '${e.invocationId}'.`),await this._sendWithProtocol(this._createCompletionMessage(e.invocationId,"Client didn't provide a result.",null))));const o=n.slice(),r=!!e.invocationId;let s,i,a;for(const n of o)try{const o=s;s=await n.apply(this,e.arguments),r&&s&&o&&(this._logger.log(Pt.Error,`Multiple results provided for '${t}'. Sending error to server.`),a=this._createCompletionMessage(e.invocationId,"Client provided multiple results.",null)),i=void 0}catch(e){i=e,this._logger.log(Pt.Error,`A callback for the method '${t}' threw error '${e}'.`)}a?await this._sendWithProtocol(a):r?(i?a=this._createCompletionMessage(e.invocationId,`${i}`,null):void 0!==s?a=this._createCompletionMessage(e.invocationId,null,s):(this._logger.log(Pt.Warning,`No result given for '${t}' method and invocation ID '${e.invocationId}'.`),a=this._createCompletionMessage(e.invocationId,"Client didn't provide a result.",null)),await this._sendWithProtocol(a)):s&&this._logger.log(Pt.Error,`Result given for '${t}' method but server is not expecting a result.`)}_connectionClosed(e){this._logger.log(Pt.Debug,`HubConnection.connectionClosed(${e}) called while in state ${this._connectionState}.`),this._stopDuringStartError=this._stopDuringStartError||e||new Yt("The underlying connection was closed before the hub handshake could complete."),this._handshakeResolver&&this._handshakeResolver(),this._cancelCallbacksWithError(e||new Error("Invocation canceled due to the underlying connection being closed.")),this._cleanupTimeout(),this._cleanupPingTimer(),this._connectionState===rn.Disconnecting?this._completeClose(e):this._connectionState===rn.Connected&&this._reconnectPolicy?this._reconnect(e):this._connectionState===rn.Connected&&this._completeClose(e)}_completeClose(e){if(this._connectionStarted){this._connectionState=rn.Disconnected,this._connectionStarted=!1,this._messageBuffer&&(this._messageBuffer._dispose(null!=e?e:new Error("Connection closed.")),this._messageBuffer=void 0),Bt.isBrowser&&window.document.removeEventListener("freeze",this._freezeEventListener);try{this._closedCallbacks.forEach((t=>t.apply(this,[e])))}catch(t){this._logger.log(Pt.Error,`An onclose callback called with error '${e}' threw error '${t}'.`)}}}async _reconnect(e){const t=Date.now();let n=0,o=void 0!==e?e:new Error("Attempting to reconnect due to a unknown error."),r=this._getNextRetryDelay(n++,0,o);if(null===r)return this._logger.log(Pt.Debug,"Connection not reconnecting because the IRetryPolicy returned null on the first reconnect attempt."),void this._completeClose(e);if(this._connectionState=rn.Reconnecting,e?this._logger.log(Pt.Information,`Connection reconnecting because of error '${e}'.`):this._logger.log(Pt.Information,"Connection reconnecting."),0!==this._reconnectingCallbacks.length){try{this._reconnectingCallbacks.forEach((t=>t.apply(this,[e])))}catch(t){this._logger.log(Pt.Error,`An onreconnecting callback called with error '${e}' threw error '${t}'.`)}if(this._connectionState!==rn.Reconnecting)return void this._logger.log(Pt.Debug,"Connection left the reconnecting state in onreconnecting callback. Done reconnecting.")}for(;null!==r;){if(this._logger.log(Pt.Information,`Reconnect attempt number ${n} will start in ${r} ms.`),await new Promise((e=>{this._reconnectDelayHandle=setTimeout(e,r)})),this._reconnectDelayHandle=void 0,this._connectionState!==rn.Reconnecting)return void this._logger.log(Pt.Debug,"Connection left the reconnecting state during reconnect delay. Done reconnecting.");try{if(await this._startInternal(),this._connectionState=rn.Connected,this._logger.log(Pt.Information,"HubConnection reconnected successfully."),0!==this._reconnectedCallbacks.length)try{this._reconnectedCallbacks.forEach((e=>e.apply(this,[this.connection.connectionId])))}catch(e){this._logger.log(Pt.Error,`An onreconnected callback called with connectionId '${this.connection.connectionId}; threw error '${e}'.`)}return}catch(e){if(this._logger.log(Pt.Information,`Reconnect attempt failed because of error '${e}'.`),this._connectionState!==rn.Reconnecting)return this._logger.log(Pt.Debug,`Connection moved to the '${this._connectionState}' from the reconnecting state during reconnect attempt. Done reconnecting.`),void(this._connectionState===rn.Disconnecting&&this._completeClose());o=e instanceof Error?e:new Error(e.toString()),r=this._getNextRetryDelay(n++,Date.now()-t,o)}}this._logger.log(Pt.Information,`Reconnect retries have been exhausted after ${Date.now()-t} ms and ${n} failed attempts. Connection disconnecting.`),this._completeClose()}_getNextRetryDelay(e,t,n){try{return this._reconnectPolicy.nextRetryDelayInMilliseconds({elapsedMilliseconds:t,previousRetryCount:e,retryReason:n})}catch(n){return this._logger.log(Pt.Error,`IRetryPolicy.nextRetryDelayInMilliseconds(${e}, ${t}) threw error '${n}'.`),null}}_cancelCallbacksWithError(e){const t=this._callbacks;this._callbacks={},Object.keys(t).forEach((n=>{const o=t[n];try{o(null,e)}catch(t){this._logger.log(Pt.Error,`Stream 'error' callback called with '${e}' threw error: ${Vt(t)}`)}}))}_cleanupPingTimer(){this._pingServerHandle&&(clearTimeout(this._pingServerHandle),this._pingServerHandle=void 0)}_cleanupTimeout(){this._timeoutHandle&&clearTimeout(this._timeoutHandle)}_createInvocation(e,t,n,o){if(n)return 0!==o.length?{arguments:t,streamIds:o,target:e,type:on.Invocation}:{arguments:t,target:e,type:on.Invocation};{const n=this._invocationId;return this._invocationId++,0!==o.length?{arguments:t,invocationId:n.toString(),streamIds:o,target:e,type:on.Invocation}:{arguments:t,invocationId:n.toString(),target:e,type:on.Invocation}}}_launchStreams(e,t){if(0!==e.length){t||(t=Promise.resolve());for(const n in e)e[n].subscribe({complete:()=>{t=t.then((()=>this._sendWithProtocol(this._createCompletionMessage(n))))},error:e=>{let o;o=e instanceof Error?e.message:e&&e.toString?e.toString():"Unknown error",t=t.then((()=>this._sendWithProtocol(this._createCompletionMessage(n,o))))},next:e=>{t=t.then((()=>this._sendWithProtocol(this._createStreamItemMessage(n,e))))}})}}_replaceStreamingParams(e){const t=[],n=[];for(let o=0;o0)&&(t=!1,this._accessToken=await this._accessTokenFactory()),this._setAuthorizationHeader(e);const n=await this._innerClient.send(e);return t&&401===n.statusCode&&this._accessTokenFactory?(this._accessToken=await this._accessTokenFactory(),this._setAuthorizationHeader(e),await this._innerClient.send(e)):n}_setAuthorizationHeader(e){e.headers||(e.headers={}),this._accessToken?e.headers[un.Authorization]=`Bearer ${this._accessToken}`:this._accessTokenFactory&&e.headers[un.Authorization]&&delete e.headers[un.Authorization]}getCookieString(e){return this._innerClient.getCookieString(e)}}class mn extends fn{constructor(e){super(),this._logger=e;const t={_fetchType:void 0,_jar:void 0};var o;o=t,"undefined"==typeof fetch&&(o._jar=new(n(628).CookieJar),"undefined"==typeof fetch?o._fetchType=n(200):o._fetchType=fetch,o._fetchType=n(203)(o._fetchType,o._jar),1)?(this._fetchType=t._fetchType,this._jar=t._jar):this._fetchType=fetch.bind(function(){if("undefined"!=typeof globalThis)return globalThis;if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if(void 0!==n.g)return n.g;throw new Error("could not find global")}()),this._abortControllerType=AbortController;const r={_abortControllerType:this._abortControllerType};(function(e){return"undefined"==typeof AbortController&&(e._abortControllerType=n(778),!0)})(r)&&(this._abortControllerType=r._abortControllerType)}async send(e){if(e.abortSignal&&e.abortSignal.aborted)throw new Yt;if(!e.method)throw new Error("No method defined.");if(!e.url)throw new Error("No url defined.");const t=new this._abortControllerType;let n;e.abortSignal&&(e.abortSignal.onabort=()=>{t.abort(),n=new Yt});let o,r=null;if(e.timeout){const o=e.timeout;r=setTimeout((()=>{t.abort(),this._logger.log(Pt.Warning,"Timeout from HTTP request."),n=new Gt}),o)}""===e.content&&(e.content=void 0),e.content&&(e.headers=e.headers||{},Ot(e.content)?e.headers["Content-Type"]="application/octet-stream":e.headers["Content-Type"]="text/plain;charset=UTF-8");try{o=await this._fetchType(e.url,{body:e.content,cache:"no-cache",credentials:!0===e.withCredentials?"include":"same-origin",headers:{"X-Requested-With":"XMLHttpRequest",...e.headers},method:e.method,mode:"cors",redirect:"follow",signal:t.signal})}catch(e){if(n)throw n;throw this._logger.log(Pt.Warning,`Error from HTTP request. ${e}.`),e}finally{r&&clearTimeout(r),e.abortSignal&&(e.abortSignal.onabort=null)}if(!o.ok){const e=await vn(o,"text");throw new Xt(e||o.statusText,o.status)}const s=vn(o,e.responseType),i=await s;return new pn(o.status,o.statusText,i)}getCookieString(e){return""}}function vn(e,t){let n;switch(t){case"arraybuffer":n=e.arrayBuffer();break;case"text":default:n=e.text();break;case"blob":case"document":case"json":throw new Error(`${t} is not supported.`)}return n}class yn extends fn{constructor(e){super(),this._logger=e}send(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new Yt):e.method?e.url?new Promise(((t,n)=>{const o=new XMLHttpRequest;o.open(e.method,e.url,!0),o.withCredentials=void 0===e.withCredentials||e.withCredentials,o.setRequestHeader("X-Requested-With","XMLHttpRequest"),""===e.content&&(e.content=void 0),e.content&&(Ot(e.content)?o.setRequestHeader("Content-Type","application/octet-stream"):o.setRequestHeader("Content-Type","text/plain;charset=UTF-8"));const r=e.headers;r&&Object.keys(r).forEach((e=>{o.setRequestHeader(e,r[e])})),e.responseType&&(o.responseType=e.responseType),e.abortSignal&&(e.abortSignal.onabort=()=>{o.abort(),n(new Yt)}),e.timeout&&(o.timeout=e.timeout),o.onload=()=>{e.abortSignal&&(e.abortSignal.onabort=null),o.status>=200&&o.status<300?t(new pn(o.status,o.statusText,o.response||o.responseText)):n(new Xt(o.response||o.responseText||o.statusText,o.status))},o.onerror=()=>{this._logger.log(Pt.Warning,`Error from HTTP request. ${o.status}: ${o.statusText}.`),n(new Xt(o.statusText,o.status))},o.ontimeout=()=>{this._logger.log(Pt.Warning,"Timeout from HTTP request."),n(new Gt)},o.send(e.content)})):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}}class wn extends fn{constructor(e){if(super(),"undefined"!=typeof fetch)this._httpClient=new mn(e);else{if("undefined"==typeof XMLHttpRequest)throw new Error("No usable HttpClient found.");this._httpClient=new yn(e)}}send(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new Yt):e.method?e.url?this._httpClient.send(e):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}getCookieString(e){return this._httpClient.getCookieString(e)}}var bn,_n;!function(e){e[e.None=0]="None",e[e.WebSockets=1]="WebSockets",e[e.ServerSentEvents=2]="ServerSentEvents",e[e.LongPolling=4]="LongPolling"}(bn||(bn={})),function(e){e[e.Text=1]="Text",e[e.Binary=2]="Binary"}(_n||(_n={}));class En{constructor(){this._isAborted=!1,this.onabort=null}abort(){this._isAborted||(this._isAborted=!0,this.onabort&&this.onabort())}get signal(){return this}get aborted(){return this._isAborted}}class Sn{get pollAborted(){return this._pollAbort.aborted}constructor(e,t,n){this._httpClient=e,this._logger=t,this._pollAbort=new En,this._options=n,this._running=!1,this.onreceive=null,this.onclose=null}async connect(e,t){if(Lt.isRequired(e,"url"),Lt.isRequired(t,"transferFormat"),Lt.isIn(t,_n,"transferFormat"),this._url=e,this._logger.log(Pt.Trace,"(LongPolling transport) Connecting."),t===_n.Binary&&"undefined"!=typeof XMLHttpRequest&&"string"!=typeof(new XMLHttpRequest).responseType)throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");const[n,o]=Wt(),r={[n]:o,...this._options.headers},s={abortSignal:this._pollAbort.signal,headers:r,timeout:1e5,withCredentials:this._options.withCredentials};t===_n.Binary&&(s.responseType="arraybuffer");const i=`${e}&_=${Date.now()}`;this._logger.log(Pt.Trace,`(LongPolling transport) polling: ${i}.`);const a=await this._httpClient.get(i,s);200!==a.statusCode?(this._logger.log(Pt.Error,`(LongPolling transport) Unexpected response code: ${a.statusCode}.`),this._closeError=new Xt(a.statusText||"",a.statusCode),this._running=!1):this._running=!0,this._receiving=this._poll(this._url,s)}async _poll(e,t){try{for(;this._running;)try{const n=`${e}&_=${Date.now()}`;this._logger.log(Pt.Trace,`(LongPolling transport) polling: ${n}.`);const o=await this._httpClient.get(n,t);204===o.statusCode?(this._logger.log(Pt.Information,"(LongPolling transport) Poll terminated by server."),this._running=!1):200!==o.statusCode?(this._logger.log(Pt.Error,`(LongPolling transport) Unexpected response code: ${o.statusCode}.`),this._closeError=new Xt(o.statusText||"",o.statusCode),this._running=!1):o.content?(this._logger.log(Pt.Trace,`(LongPolling transport) data received. ${Ft(o.content,this._options.logMessageContent)}.`),this.onreceive&&this.onreceive(o.content)):this._logger.log(Pt.Trace,"(LongPolling transport) Poll timed out, reissuing.")}catch(e){this._running?e instanceof Gt?this._logger.log(Pt.Trace,"(LongPolling transport) Poll timed out, reissuing."):(this._closeError=e,this._running=!1):this._logger.log(Pt.Trace,`(LongPolling transport) Poll errored after shutdown: ${e.message}`)}}finally{this._logger.log(Pt.Trace,"(LongPolling transport) Polling complete."),this.pollAborted||this._raiseOnClose()}}async send(e){return this._running?$t(this._logger,"LongPolling",this._httpClient,this._url,e,this._options):Promise.reject(new Error("Cannot send until the transport is connected"))}async stop(){this._logger.log(Pt.Trace,"(LongPolling transport) Stopping polling."),this._running=!1,this._pollAbort.abort();try{await this._receiving,this._logger.log(Pt.Trace,`(LongPolling transport) sending DELETE request to ${this._url}.`);const e={},[t,n]=Wt();e[t]=n;const o={headers:{...e,...this._options.headers},timeout:this._options.timeout,withCredentials:this._options.withCredentials};let r;try{await this._httpClient.delete(this._url,o)}catch(e){r=e}r?r instanceof Xt&&(404===r.statusCode?this._logger.log(Pt.Trace,"(LongPolling transport) A 404 response was returned from sending a DELETE request."):this._logger.log(Pt.Trace,`(LongPolling transport) Error sending a DELETE request: ${r}`)):this._logger.log(Pt.Trace,"(LongPolling transport) DELETE request accepted.")}finally{this._logger.log(Pt.Trace,"(LongPolling transport) Stop finished."),this._raiseOnClose()}}_raiseOnClose(){if(this.onclose){let e="(LongPolling transport) Firing onclose event.";this._closeError&&(e+=" Error: "+this._closeError),this._logger.log(Pt.Trace,e),this.onclose(this._closeError)}}}class Cn{constructor(e,t,n,o){this._httpClient=e,this._accessToken=t,this._logger=n,this._options=o,this.onreceive=null,this.onclose=null}async connect(e,t){return Lt.isRequired(e,"url"),Lt.isRequired(t,"transferFormat"),Lt.isIn(t,_n,"transferFormat"),this._logger.log(Pt.Trace,"(SSE transport) Connecting."),this._url=e,this._accessToken&&(e+=(e.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(this._accessToken)}`),new Promise(((n,o)=>{let r,s=!1;if(t===_n.Text){if(Bt.isBrowser||Bt.isWebWorker)r=new this._options.EventSource(e,{withCredentials:this._options.withCredentials});else{const t=this._httpClient.getCookieString(e),n={};n.Cookie=t;const[o,s]=Wt();n[o]=s,r=new this._options.EventSource(e,{withCredentials:this._options.withCredentials,headers:{...n,...this._options.headers}})}try{r.onmessage=e=>{if(this.onreceive)try{this._logger.log(Pt.Trace,`(SSE transport) data received. ${Ft(e.data,this._options.logMessageContent)}.`),this.onreceive(e.data)}catch(e){return void this._close(e)}},r.onerror=e=>{s?this._close():o(new Error("EventSource failed to connect. The connection could not be found on the server, either the connection ID is not present on the server, or a proxy is refusing/buffering the connection. If you have multiple servers check that sticky sessions are enabled."))},r.onopen=()=>{this._logger.log(Pt.Information,`SSE connected to ${this._url}`),this._eventSource=r,s=!0,n()}}catch(e){return void o(e)}}else o(new Error("The Server-Sent Events transport only supports the 'Text' transfer format"))}))}async send(e){return this._eventSource?$t(this._logger,"SSE",this._httpClient,this._url,e,this._options):Promise.reject(new Error("Cannot send until the transport is connected"))}stop(){return this._close(),Promise.resolve()}_close(e){this._eventSource&&(this._eventSource.close(),this._eventSource=void 0,this.onclose&&this.onclose(e))}}class In{constructor(e,t,n,o,r,s){this._logger=n,this._accessTokenFactory=t,this._logMessageContent=o,this._webSocketConstructor=r,this._httpClient=e,this.onreceive=null,this.onclose=null,this._headers=s}async connect(e,t){let n;return Lt.isRequired(e,"url"),Lt.isRequired(t,"transferFormat"),Lt.isIn(t,_n,"transferFormat"),this._logger.log(Pt.Trace,"(WebSockets transport) Connecting."),this._accessTokenFactory&&(n=await this._accessTokenFactory()),new Promise(((o,r)=>{let s;e=e.replace(/^http/,"ws");const i=this._httpClient.getCookieString(e);let a=!1;if(Bt.isReactNative){const t={},[o,r]=Wt();t[o]=r,n&&(t[un.Authorization]=`Bearer ${n}`),i&&(t[un.Cookie]=i),s=new this._webSocketConstructor(e,void 0,{headers:{...t,...this._headers}})}else n&&(e+=(e.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(n)}`);s||(s=new this._webSocketConstructor(e)),t===_n.Binary&&(s.binaryType="arraybuffer"),s.onopen=t=>{this._logger.log(Pt.Information,`WebSocket connected to ${e}.`),this._webSocket=s,a=!0,o()},s.onerror=e=>{let t=null;t="undefined"!=typeof ErrorEvent&&e instanceof ErrorEvent?e.error:"There was an error with the transport",this._logger.log(Pt.Information,`(WebSockets transport) ${t}.`)},s.onmessage=e=>{if(this._logger.log(Pt.Trace,`(WebSockets transport) data received. ${Ft(e.data,this._logMessageContent)}.`),this.onreceive)try{this.onreceive(e.data)}catch(e){return void this._close(e)}},s.onclose=e=>{if(a)this._close(e);else{let t=null;t="undefined"!=typeof ErrorEvent&&e instanceof ErrorEvent?e.error:"WebSocket failed to connect. The connection could not be found on the server, either the endpoint may not be a SignalR endpoint, the connection ID is not present on the server, or there is a proxy blocking WebSockets. If you have multiple servers check that sticky sessions are enabled.",r(new Error(t))}}}))}send(e){return this._webSocket&&this._webSocket.readyState===this._webSocketConstructor.OPEN?(this._logger.log(Pt.Trace,`(WebSockets transport) sending data. ${Ft(e,this._logMessageContent)}.`),this._webSocket.send(e),Promise.resolve()):Promise.reject("WebSocket is not in the OPEN state")}stop(){return this._webSocket&&this._close(void 0),Promise.resolve()}_close(e){this._webSocket&&(this._webSocket.onclose=()=>{},this._webSocket.onmessage=()=>{},this._webSocket.onerror=()=>{},this._webSocket.close(),this._webSocket=void 0),this._logger.log(Pt.Trace,"(WebSockets transport) socket closed."),this.onclose&&(!this._isCloseEvent(e)||!1!==e.wasClean&&1e3===e.code?e instanceof Error?this.onclose(e):this.onclose():this.onclose(new Error(`WebSocket closed with status code: ${e.code} (${e.reason||"no reason given"}).`)))}_isCloseEvent(e){return e&&"boolean"==typeof e.wasClean&&"number"==typeof e.code}}class kn{constructor(e,t={}){if(this._stopPromiseResolver=()=>{},this.features={},this._negotiateVersion=1,Lt.isRequired(e,"url"),this._logger=function(e){return void 0===e?new jt(Pt.Information):null===e?Mt.instance:void 0!==e.log?e:new jt(e)}(t.logger),this.baseUrl=this._resolveUrl(e),(t=t||{}).logMessageContent=void 0!==t.logMessageContent&&t.logMessageContent,"boolean"!=typeof t.withCredentials&&void 0!==t.withCredentials)throw new Error("withCredentials option was not a 'boolean' or 'undefined' value");t.withCredentials=void 0===t.withCredentials||t.withCredentials,t.timeout=void 0===t.timeout?1e5:t.timeout,"undefined"==typeof WebSocket||t.WebSocket||(t.WebSocket=WebSocket),"undefined"==typeof EventSource||t.EventSource||(t.EventSource=EventSource),this._httpClient=new gn(t.httpClient||new wn(this._logger),t.accessTokenFactory),this._connectionState="Disconnected",this._connectionStarted=!1,this._options=t,this.onreceive=null,this.onclose=null}async start(e){if(e=e||_n.Binary,Lt.isIn(e,_n,"transferFormat"),this._logger.log(Pt.Debug,`Starting connection with transfer format '${_n[e]}'.`),"Disconnected"!==this._connectionState)return Promise.reject(new Error("Cannot start an HttpConnection that is not in the 'Disconnected' state."));if(this._connectionState="Connecting",this._startInternalPromise=this._startInternal(e),await this._startInternalPromise,"Disconnecting"===this._connectionState){const e="Failed to start the HttpConnection before stop() was called.";return this._logger.log(Pt.Error,e),await this._stopPromise,Promise.reject(new Yt(e))}if("Connected"!==this._connectionState){const e="HttpConnection.startInternal completed gracefully but didn't enter the connection into the connected state!";return this._logger.log(Pt.Error,e),Promise.reject(new Yt(e))}this._connectionStarted=!0}send(e){return"Connected"!==this._connectionState?Promise.reject(new Error("Cannot send data if the connection is not in the 'Connected' State.")):(this._sendQueue||(this._sendQueue=new Tn(this.transport)),this._sendQueue.send(e))}async stop(e){return"Disconnected"===this._connectionState?(this._logger.log(Pt.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnected state.`),Promise.resolve()):"Disconnecting"===this._connectionState?(this._logger.log(Pt.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnecting state.`),this._stopPromise):(this._connectionState="Disconnecting",this._stopPromise=new Promise((e=>{this._stopPromiseResolver=e})),await this._stopInternal(e),void await this._stopPromise)}async _stopInternal(e){this._stopError=e;try{await this._startInternalPromise}catch(e){}if(this.transport){try{await this.transport.stop()}catch(e){this._logger.log(Pt.Error,`HttpConnection.transport.stop() threw error '${e}'.`),this._stopConnection()}this.transport=void 0}else this._logger.log(Pt.Debug,"HttpConnection.transport is undefined in HttpConnection.stop() because start() failed.")}async _startInternal(e){let t=this.baseUrl;this._accessTokenFactory=this._options.accessTokenFactory,this._httpClient._accessTokenFactory=this._accessTokenFactory;try{if(this._options.skipNegotiation){if(this._options.transport!==bn.WebSockets)throw new Error("Negotiation can only be skipped when using the WebSocket transport directly.");this.transport=this._constructTransport(bn.WebSockets),await this._startTransport(t,e)}else{let n=null,o=0;do{if(n=await this._getNegotiationResponse(t),"Disconnecting"===this._connectionState||"Disconnected"===this._connectionState)throw new Yt("The connection was stopped during negotiation.");if(n.error)throw new Error(n.error);if(n.ProtocolVersion)throw new Error("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.");if(n.url&&(t=n.url),n.accessToken){const e=n.accessToken;this._accessTokenFactory=()=>e,this._httpClient._accessToken=e,this._httpClient._accessTokenFactory=void 0}o++}while(n.url&&o<100);if(100===o&&n.url)throw new Error("Negotiate redirection limit exceeded.");await this._createTransport(t,this._options.transport,n,e)}this.transport instanceof Sn&&(this.features.inherentKeepAlive=!0),"Connecting"===this._connectionState&&(this._logger.log(Pt.Debug,"The HttpConnection connected successfully."),this._connectionState="Connected")}catch(e){return this._logger.log(Pt.Error,"Failed to start the connection: "+e),this._connectionState="Disconnected",this.transport=void 0,this._stopPromiseResolver(),Promise.reject(e)}}async _getNegotiationResponse(e){const t={},[n,o]=Wt();t[n]=o;const r=this._resolveNegotiateUrl(e);this._logger.log(Pt.Debug,`Sending negotiation request: ${r}.`);try{const e=await this._httpClient.post(r,{content:"",headers:{...t,...this._options.headers},timeout:this._options.timeout,withCredentials:this._options.withCredentials});if(200!==e.statusCode)return Promise.reject(new Error(`Unexpected status code returned from negotiate '${e.statusCode}'`));const n=JSON.parse(e.content);return(!n.negotiateVersion||n.negotiateVersion<1)&&(n.connectionToken=n.connectionId),n.useStatefulReconnect&&!0!==this._options._useStatefulReconnect?Promise.reject(new tn("Client didn't negotiate Stateful Reconnect but the server did.")):n}catch(e){let t="Failed to complete negotiation with the server: "+e;return e instanceof Xt&&404===e.statusCode&&(t+=" Either this is not a SignalR endpoint or there is a proxy blocking the connection."),this._logger.log(Pt.Error,t),Promise.reject(new tn(t))}}_createConnectUrl(e,t){return t?e+(-1===e.indexOf("?")?"?":"&")+`id=${t}`:e}async _createTransport(e,t,n,o){let r=this._createConnectUrl(e,n.connectionToken);if(this._isITransport(t))return this._logger.log(Pt.Debug,"Connection was provided an instance of ITransport, using that directly."),this.transport=t,await this._startTransport(r,o),void(this.connectionId=n.connectionId);const s=[],i=n.availableTransports||[];let a=n;for(const n of i){const i=this._resolveTransportOrError(n,t,o,!0===(null==a?void 0:a.useStatefulReconnect));if(i instanceof Error)s.push(`${n.transport} failed:`),s.push(i);else if(this._isITransport(i)){if(this.transport=i,!a){try{a=await this._getNegotiationResponse(e)}catch(e){return Promise.reject(e)}r=this._createConnectUrl(e,a.connectionToken)}try{return await this._startTransport(r,o),void(this.connectionId=a.connectionId)}catch(e){if(this._logger.log(Pt.Error,`Failed to start the transport '${n.transport}': ${e}`),a=void 0,s.push(new en(`${n.transport} failed: ${e}`,bn[n.transport])),"Connecting"!==this._connectionState){const e="Failed to select transport before stop() was called.";return this._logger.log(Pt.Debug,e),Promise.reject(new Yt(e))}}}}return s.length>0?Promise.reject(new nn(`Unable to connect to the server with any of the available transports. ${s.join(" ")}`,s)):Promise.reject(new Error("None of the transports supported by the client are supported by the server."))}_constructTransport(e){switch(e){case bn.WebSockets:if(!this._options.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new In(this._httpClient,this._accessTokenFactory,this._logger,this._options.logMessageContent,this._options.WebSocket,this._options.headers||{});case bn.ServerSentEvents:if(!this._options.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new Cn(this._httpClient,this._httpClient._accessToken,this._logger,this._options);case bn.LongPolling:return new Sn(this._httpClient,this._logger,this._options);default:throw new Error(`Unknown transport: ${e}.`)}}_startTransport(e,t){return this.transport.onreceive=this.onreceive,this.features.reconnect?this.transport.onclose=async n=>{let o=!1;if(this.features.reconnect){try{this.features.disconnected(),await this.transport.connect(e,t),await this.features.resend()}catch{o=!0}o&&this._stopConnection(n)}else this._stopConnection(n)}:this.transport.onclose=e=>this._stopConnection(e),this.transport.connect(e,t)}_resolveTransportOrError(e,t,n,o){const r=bn[e.transport];if(null==r)return this._logger.log(Pt.Debug,`Skipping transport '${e.transport}' because it is not supported by this client.`),new Error(`Skipping transport '${e.transport}' because it is not supported by this client.`);if(!function(e,t){return!e||0!=(t&e)}(t,r))return this._logger.log(Pt.Debug,`Skipping transport '${bn[r]}' because it was disabled by the client.`),new Zt(`'${bn[r]}' is disabled by the client.`,r);if(!(e.transferFormats.map((e=>_n[e])).indexOf(n)>=0))return this._logger.log(Pt.Debug,`Skipping transport '${bn[r]}' because it does not support the requested transfer format '${_n[n]}'.`),new Error(`'${bn[r]}' does not support ${_n[n]}.`);if(r===bn.WebSockets&&!this._options.WebSocket||r===bn.ServerSentEvents&&!this._options.EventSource)return this._logger.log(Pt.Debug,`Skipping transport '${bn[r]}' because it is not supported in your environment.'`),new Qt(`'${bn[r]}' is not supported in your environment.`,r);this._logger.log(Pt.Debug,`Selecting transport '${bn[r]}'.`);try{return this.features.reconnect=r===bn.WebSockets?o:void 0,this._constructTransport(r)}catch(e){return e}}_isITransport(e){return e&&"object"==typeof e&&"connect"in e}_stopConnection(e){if(this._logger.log(Pt.Debug,`HttpConnection.stopConnection(${e}) called while in state ${this._connectionState}.`),this.transport=void 0,e=this._stopError||e,this._stopError=void 0,"Disconnected"!==this._connectionState){if("Connecting"===this._connectionState)throw this._logger.log(Pt.Warning,`Call to HttpConnection.stopConnection(${e}) was ignored because the connection is still in the connecting state.`),new Error(`HttpConnection.stopConnection(${e}) was called while the connection is still in the connecting state.`);if("Disconnecting"===this._connectionState&&this._stopPromiseResolver(),e?this._logger.log(Pt.Error,`Connection disconnected with error '${e}'.`):this._logger.log(Pt.Information,"Connection disconnected."),this._sendQueue&&(this._sendQueue.stop().catch((e=>{this._logger.log(Pt.Error,`TransportSendQueue.stop() threw error '${e}'.`)})),this._sendQueue=void 0),this.connectionId=void 0,this._connectionState="Disconnected",this._connectionStarted){this._connectionStarted=!1;try{this.onclose&&this.onclose(e)}catch(t){this._logger.log(Pt.Error,`HttpConnection.onclose(${e}) threw error '${t}'.`)}}}else this._logger.log(Pt.Debug,`Call to HttpConnection.stopConnection(${e}) was ignored because the connection is already in the disconnected state.`)}_resolveUrl(e){if(0===e.lastIndexOf("https://",0)||0===e.lastIndexOf("http://",0))return e;if(!Bt.isBrowser)throw new Error(`Cannot resolve '${e}'.`);const t=window.document.createElement("a");return t.href=e,this._logger.log(Pt.Information,`Normalizing '${e}' to '${t.href}'.`),t.href}_resolveNegotiateUrl(e){const t=new URL(e);t.pathname.endsWith("/")?t.pathname+="negotiate":t.pathname+="/negotiate";const n=new URLSearchParams(t.searchParams);return n.has("negotiateVersion")||n.append("negotiateVersion",this._negotiateVersion.toString()),n.has("useStatefulReconnect")?"true"===n.get("useStatefulReconnect")&&(this._options._useStatefulReconnect=!0):!0===this._options._useStatefulReconnect&&n.append("useStatefulReconnect","true"),t.search=n.toString(),t.toString()}}class Tn{constructor(e){this._transport=e,this._buffer=[],this._executing=!0,this._sendBufferedData=new Dn,this._transportResult=new Dn,this._sendLoopPromise=this._sendLoop()}send(e){return this._bufferData(e),this._transportResult||(this._transportResult=new Dn),this._transportResult.promise}stop(){return this._executing=!1,this._sendBufferedData.resolve(),this._sendLoopPromise}_bufferData(e){if(this._buffer.length&&typeof this._buffer[0]!=typeof e)throw new Error(`Expected data to be of type ${typeof this._buffer} but was of type ${typeof e}`);this._buffer.push(e),this._sendBufferedData.resolve()}async _sendLoop(){for(;;){if(await this._sendBufferedData.promise,!this._executing){this._transportResult&&this._transportResult.reject("Connection stopped.");break}this._sendBufferedData=new Dn;const e=this._transportResult;this._transportResult=void 0;const t="string"==typeof this._buffer[0]?this._buffer.join(""):Tn._concatBuffers(this._buffer);this._buffer.length=0;try{await this._transport.send(t),e.resolve()}catch(t){e.reject(t)}}}static _concatBuffers(e){const t=e.map((e=>e.byteLength)).reduce(((e,t)=>e+t)),n=new Uint8Array(t);let o=0;for(const t of e)n.set(new Uint8Array(t),o),o+=t.byteLength;return n.buffer}}class Dn{constructor(){this.promise=new Promise(((e,t)=>[this._resolver,this._rejecter]=[e,t]))}resolve(){this._resolver()}reject(e){this._rejecter(e)}}class Rn{constructor(){this.name="json",this.version=2,this.transferFormat=_n.Text}parseMessages(e,t){if("string"!=typeof e)throw new Error("Invalid input for JSON hub protocol. Expected a string.");if(!e)return[];null===t&&(t=Mt.instance);const n=Nt.parse(e),o=[];for(const e of n){const n=JSON.parse(e);if("number"!=typeof n.type)throw new Error("Invalid payload.");switch(n.type){case on.Invocation:this._isInvocationMessage(n);break;case on.StreamItem:this._isStreamItemMessage(n);break;case on.Completion:this._isCompletionMessage(n);break;case on.Ping:case on.Close:break;case on.Ack:this._isAckMessage(n);break;case on.Sequence:this._isSequenceMessage(n);break;default:t.log(Pt.Information,"Unknown message type '"+n.type+"' ignored.");continue}o.push(n)}return o}writeMessage(e){return Nt.write(JSON.stringify(e))}_isInvocationMessage(e){this._assertNotEmptyString(e.target,"Invalid payload for Invocation message."),void 0!==e.invocationId&&this._assertNotEmptyString(e.invocationId,"Invalid payload for Invocation message.")}_isStreamItemMessage(e){if(this._assertNotEmptyString(e.invocationId,"Invalid payload for StreamItem message."),void 0===e.item)throw new Error("Invalid payload for StreamItem message.")}_isCompletionMessage(e){if(e.result&&e.error)throw new Error("Invalid payload for Completion message.");!e.result&&e.error&&this._assertNotEmptyString(e.error,"Invalid payload for Completion message."),this._assertNotEmptyString(e.invocationId,"Invalid payload for Completion message.")}_isAckMessage(e){if("number"!=typeof e.sequenceId)throw new Error("Invalid SequenceId for Ack message.")}_isSequenceMessage(e){if("number"!=typeof e.sequenceId)throw new Error("Invalid SequenceId for Sequence message.")}_assertNotEmptyString(e,t){if("string"!=typeof e||""===e)throw new Error(t)}}const xn={trace:Pt.Trace,debug:Pt.Debug,info:Pt.Information,information:Pt.Information,warn:Pt.Warning,warning:Pt.Warning,error:Pt.Error,critical:Pt.Critical,none:Pt.None};class An{configureLogging(e){if(Lt.isRequired(e,"logging"),function(e){return void 0!==e.log}(e))this.logger=e;else if("string"==typeof e){const t=function(e){const t=xn[e.toLowerCase()];if(void 0!==t)return t;throw new Error(`Unknown log level: ${e}`)}(e);this.logger=new jt(t)}else this.logger=new jt(e);return this}withUrl(e,t){return Lt.isRequired(e,"url"),Lt.isNotEmpty(e,"url"),this.url=e,this.httpConnectionOptions="object"==typeof t?{...this.httpConnectionOptions,...t}:{...this.httpConnectionOptions,transport:t},this}withHubProtocol(e){return Lt.isRequired(e,"protocol"),this.protocol=e,this}withAutomaticReconnect(e){if(this.reconnectPolicy)throw new Error("A reconnectPolicy has already been set.");return e?Array.isArray(e)?this.reconnectPolicy=new dn(e):this.reconnectPolicy=e:this.reconnectPolicy=new dn,this}withServerTimeout(e){return Lt.isRequired(e,"milliseconds"),this._serverTimeoutInMilliseconds=e,this}withKeepAliveInterval(e){return Lt.isRequired(e,"milliseconds"),this._keepAliveIntervalInMilliseconds=e,this}withStatefulReconnect(e){return void 0===this.httpConnectionOptions&&(this.httpConnectionOptions={}),this.httpConnectionOptions._useStatefulReconnect=!0,this._statefulReconnectBufferSize=null==e?void 0:e.bufferSize,this}build(){const e=this.httpConnectionOptions||{};if(void 0===e.logger&&(e.logger=this.logger),!this.url)throw new Error("The 'HubConnectionBuilder.withUrl' method must be called before building the connection.");const t=new kn(this.url,e);return ln.create(t,this.logger||Mt.instance,this.protocol||new Rn,this.reconnectPolicy,this._serverTimeoutInMilliseconds,this._keepAliveIntervalInMilliseconds,this._statefulReconnectBufferSize)}}var Nn;!function(e){e[e.Default=0]="Default",e[e.Server=1]="Server",e[e.WebAssembly=2]="WebAssembly",e[e.WebView=3]="WebView"}(Nn||(Nn={}));var Pn,Mn,Un,Ln=4294967295;function Bn(e,t,n){var o=Math.floor(n/4294967296),r=n;e.setUint32(t,o),e.setUint32(t+4,r)}function Fn(e,t){return 4294967296*e.getInt32(t)+e.getUint32(t+4)}var On=("undefined"==typeof process||"never"!==(null===(Pn=null===process||void 0===process?void 0:process.env)||void 0===Pn?void 0:Pn.TEXT_ENCODING))&&"undefined"!=typeof TextEncoder&&"undefined"!=typeof TextDecoder;function $n(e){for(var t=e.length,n=0,o=0;o=55296&&r<=56319&&o65535&&(h-=65536,s.push(h>>>10&1023|55296),h=56320|1023&h),s.push(h)}else s.push(a);s.length>=4096&&(i+=String.fromCharCode.apply(String,s),s.length=0)}return s.length>0&&(i+=String.fromCharCode.apply(String,s)),i}var zn,Jn=On?new TextDecoder:null,Vn=On?"undefined"!=typeof process&&"force"!==(null===(Un=null===process||void 0===process?void 0:process.env)||void 0===Un?void 0:Un.TEXT_DECODER)?200:0:Ln,Kn=function(e,t){this.type=e,this.data=t},Xn=(zn=function(e,t){return zn=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},zn(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}zn(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),Gn=function(e){function t(n){var o=e.call(this,n)||this,r=Object.create(t.prototype);return Object.setPrototypeOf(o,r),Object.defineProperty(o,"name",{configurable:!0,enumerable:!1,value:t.name}),o}return Xn(t,e),t}(Error),Yn={type:-1,encode:function(e){var t,n,o,r;return e instanceof Date?function(e){var t,n=e.sec,o=e.nsec;if(n>=0&&o>=0&&n<=17179869183){if(0===o&&n<=4294967295){var r=new Uint8Array(4);return(t=new DataView(r.buffer)).setUint32(0,n),r}var s=n/4294967296,i=4294967295&n;return r=new Uint8Array(8),(t=new DataView(r.buffer)).setUint32(0,o<<2|3&s),t.setUint32(4,i),r}return r=new Uint8Array(12),(t=new DataView(r.buffer)).setUint32(0,o),Bn(t,4,n),r}((o=1e6*((t=e.getTime())-1e3*(n=Math.floor(t/1e3))),{sec:n+(r=Math.floor(o/1e9)),nsec:o-1e9*r})):null},decode:function(e){var t=function(e){var t=new DataView(e.buffer,e.byteOffset,e.byteLength);switch(e.byteLength){case 4:return{sec:t.getUint32(0),nsec:0};case 8:var n=t.getUint32(0);return{sec:4294967296*(3&n)+t.getUint32(4),nsec:n>>>2};case 12:return{sec:Fn(t,4),nsec:t.getUint32(0)};default:throw new Gn("Unrecognized data size for timestamp (expected 4, 8, or 12): ".concat(e.length))}}(e);return new Date(1e3*t.sec+t.nsec/1e6)}},Qn=function(){function e(){this.builtInEncoders=[],this.builtInDecoders=[],this.encoders=[],this.decoders=[],this.register(Yn)}return e.prototype.register=function(e){var t=e.type,n=e.encode,o=e.decode;if(t>=0)this.encoders[t]=n,this.decoders[t]=o;else{var r=1+t;this.builtInEncoders[r]=n,this.builtInDecoders[r]=o}},e.prototype.tryToEncode=function(e,t){for(var n=0;nthis.maxDepth)throw new Error("Too deep objects in depth ".concat(t));null==e?this.encodeNil():"boolean"==typeof e?this.encodeBoolean(e):"number"==typeof e?this.encodeNumber(e):"string"==typeof e?this.encodeString(e):this.encodeObject(e,t)},e.prototype.ensureBufferSizeToWrite=function(e){var t=this.pos+e;this.view.byteLength=0?e<128?this.writeU8(e):e<256?(this.writeU8(204),this.writeU8(e)):e<65536?(this.writeU8(205),this.writeU16(e)):e<4294967296?(this.writeU8(206),this.writeU32(e)):(this.writeU8(207),this.writeU64(e)):e>=-32?this.writeU8(224|e+32):e>=-128?(this.writeU8(208),this.writeI8(e)):e>=-32768?(this.writeU8(209),this.writeI16(e)):e>=-2147483648?(this.writeU8(210),this.writeI32(e)):(this.writeU8(211),this.writeI64(e)):this.forceFloat32?(this.writeU8(202),this.writeF32(e)):(this.writeU8(203),this.writeF64(e))},e.prototype.writeStringHeader=function(e){if(e<32)this.writeU8(160+e);else if(e<256)this.writeU8(217),this.writeU8(e);else if(e<65536)this.writeU8(218),this.writeU16(e);else{if(!(e<4294967296))throw new Error("Too long string: ".concat(e," bytes in UTF-8"));this.writeU8(219),this.writeU32(e)}},e.prototype.encodeString=function(e){if(e.length>jn){var t=$n(e);this.ensureBufferSizeToWrite(5+t),this.writeStringHeader(t),Wn(e,this.bytes,this.pos),this.pos+=t}else t=$n(e),this.ensureBufferSizeToWrite(5+t),this.writeStringHeader(t),function(e,t,n){for(var o=e.length,r=n,s=0;s>6&31|192;else{if(i>=55296&&i<=56319&&s>12&15|224,t[r++]=i>>6&63|128):(t[r++]=i>>18&7|240,t[r++]=i>>12&63|128,t[r++]=i>>6&63|128)}t[r++]=63&i|128}else t[r++]=i}}(e,this.bytes,this.pos),this.pos+=t},e.prototype.encodeObject=function(e,t){var n=this.extensionCodec.tryToEncode(e,this.context);if(null!=n)this.encodeExtension(n);else if(Array.isArray(e))this.encodeArray(e,t);else if(ArrayBuffer.isView(e))this.encodeBinary(e);else{if("object"!=typeof e)throw new Error("Unrecognized object: ".concat(Object.prototype.toString.apply(e)));this.encodeMap(e,t)}},e.prototype.encodeBinary=function(e){var t=e.byteLength;if(t<256)this.writeU8(196),this.writeU8(t);else if(t<65536)this.writeU8(197),this.writeU16(t);else{if(!(t<4294967296))throw new Error("Too large binary: ".concat(t));this.writeU8(198),this.writeU32(t)}var n=Zn(e);this.writeU8a(n)},e.prototype.encodeArray=function(e,t){var n=e.length;if(n<16)this.writeU8(144+n);else if(n<65536)this.writeU8(220),this.writeU16(n);else{if(!(n<4294967296))throw new Error("Too large array: ".concat(n));this.writeU8(221),this.writeU32(n)}for(var o=0,r=e;o0&&e<=this.maxKeyLength},e.prototype.find=function(e,t,n){e:for(var o=0,r=this.caches[n-1];o=this.maxLengthPerKey?n[Math.random()*n.length|0]=o:n.push(o)},e.prototype.decode=function(e,t,n){var o=this.find(e,t,n);if(null!=o)return this.hit++,o;this.miss++;var r=qn(e,t,n),s=Uint8Array.prototype.slice.call(e,t,t+n);return this.store(s,r),r},e}(),oo=function(e,t){var n,o,r,s,i={label:0,sent:function(){if(1&r[0])throw r[1];return r[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,o&&(r=2&s[0]?o.return:s[0]?o.throw||((r=o.return)&&r.call(o),0):o.next)&&!(r=r.call(o,s[1])).done)return r;switch(o=0,r&&(s=[2&s[0],r.value]),s[0]){case 0:case 1:r=s;break;case 4:return i.label++,{value:s[1],done:!1};case 5:i.label++,o=s[1],s=[0];continue;case 7:s=i.ops.pop(),i.trys.pop();continue;default:if(!((r=(r=i.trys).length>0&&r[r.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!r||s[1]>r[0]&&s[1]=e},e.prototype.createExtraByteError=function(e){var t=this.view,n=this.pos;return new RangeError("Extra ".concat(t.byteLength-n," of ").concat(t.byteLength," byte(s) found at buffer[").concat(e,"]"))},e.prototype.decode=function(e){this.reinitializeState(),this.setBuffer(e);var t=this.doDecodeSync();if(this.hasRemaining(1))throw this.createExtraByteError(this.pos);return t},e.prototype.decodeMulti=function(e){return oo(this,(function(t){switch(t.label){case 0:this.reinitializeState(),this.setBuffer(e),t.label=1;case 1:return this.hasRemaining(1)?[4,this.doDecodeSync()]:[3,3];case 2:return t.sent(),[3,1];case 3:return[2]}}))},e.prototype.decodeAsync=function(e){var t,n,o,r,s,i,a;return s=this,void 0,a=function(){var s,i,a,c,l,h,d,u;return oo(this,(function(p){switch(p.label){case 0:s=!1,p.label=1;case 1:p.trys.push([1,6,7,12]),t=ro(e),p.label=2;case 2:return[4,t.next()];case 3:if((n=p.sent()).done)return[3,5];if(a=n.value,s)throw this.createExtraByteError(this.totalPos);this.appendBuffer(a);try{i=this.doDecodeSync(),s=!0}catch(e){if(!(e instanceof co))throw e}this.totalPos+=this.pos,p.label=4;case 4:return[3,2];case 5:return[3,12];case 6:return c=p.sent(),o={error:c},[3,12];case 7:return p.trys.push([7,,10,11]),n&&!n.done&&(r=t.return)?[4,r.call(t)]:[3,9];case 8:p.sent(),p.label=9;case 9:return[3,11];case 10:if(o)throw o.error;return[7];case 11:return[7];case 12:if(s){if(this.hasRemaining(1))throw this.createExtraByteError(this.totalPos);return[2,i]}throw h=(l=this).headByte,d=l.pos,u=l.totalPos,new RangeError("Insufficient data in parsing ".concat(to(h)," at ").concat(u," (").concat(d," in the current buffer)"))}}))},new((i=void 0)||(i=Promise))((function(e,t){function n(e){try{r(a.next(e))}catch(e){t(e)}}function o(e){try{r(a.throw(e))}catch(e){t(e)}}function r(t){var r;t.done?e(t.value):(r=t.value,r instanceof i?r:new i((function(e){e(r)}))).then(n,o)}r((a=a.apply(s,[])).next())}))},e.prototype.decodeArrayStream=function(e){return this.decodeMultiAsync(e,!0)},e.prototype.decodeStream=function(e){return this.decodeMultiAsync(e,!1)},e.prototype.decodeMultiAsync=function(e,t){return function(n,o,r){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var s,i=function(){var n,o,r,s,i,a,c,l,h;return oo(this,(function(d){switch(d.label){case 0:n=t,o=-1,d.label=1;case 1:d.trys.push([1,13,14,19]),r=ro(e),d.label=2;case 2:return[4,so(r.next())];case 3:if((s=d.sent()).done)return[3,12];if(i=s.value,t&&0===o)throw this.createExtraByteError(this.totalPos);this.appendBuffer(i),n&&(o=this.readArraySize(),n=!1,this.complete()),d.label=4;case 4:d.trys.push([4,9,,10]),d.label=5;case 5:return[4,so(this.doDecodeSync())];case 6:return[4,d.sent()];case 7:return d.sent(),0==--o?[3,8]:[3,5];case 8:return[3,10];case 9:if(!((a=d.sent())instanceof co))throw a;return[3,10];case 10:this.totalPos+=this.pos,d.label=11;case 11:return[3,2];case 12:return[3,19];case 13:return c=d.sent(),l={error:c},[3,19];case 14:return d.trys.push([14,,17,18]),s&&!s.done&&(h=r.return)?[4,so(h.call(r))]:[3,16];case 15:d.sent(),d.label=16;case 16:return[3,18];case 17:if(l)throw l.error;return[7];case 18:return[7];case 19:return[2]}}))}.apply(n,o||[]),a=[];return s={},c("next"),c("throw"),c("return"),s[Symbol.asyncIterator]=function(){return this},s;function c(e){i[e]&&(s[e]=function(t){return new Promise((function(n,o){a.push([e,t,n,o])>1||l(e,t)}))})}function l(e,t){try{(n=i[e](t)).value instanceof so?Promise.resolve(n.value.v).then(h,d):u(a[0][2],n)}catch(e){u(a[0][3],e)}var n}function h(e){l("next",e)}function d(e){l("throw",e)}function u(e,t){e(t),a.shift(),a.length&&l(a[0][0],a[0][1])}}(this,arguments)},e.prototype.doDecodeSync=function(){e:for(;;){var e=this.readHeadByte(),t=void 0;if(e>=224)t=e-256;else if(e<192)if(e<128)t=e;else if(e<144){if(0!=(o=e-128)){this.pushMapState(o),this.complete();continue e}t={}}else if(e<160){if(0!=(o=e-144)){this.pushArrayState(o),this.complete();continue e}t=[]}else{var n=e-160;t=this.decodeUtf8String(n,0)}else if(192===e)t=null;else if(194===e)t=!1;else if(195===e)t=!0;else if(202===e)t=this.readF32();else if(203===e)t=this.readF64();else if(204===e)t=this.readU8();else if(205===e)t=this.readU16();else if(206===e)t=this.readU32();else if(207===e)t=this.readU64();else if(208===e)t=this.readI8();else if(209===e)t=this.readI16();else if(210===e)t=this.readI32();else if(211===e)t=this.readI64();else if(217===e)n=this.lookU8(),t=this.decodeUtf8String(n,1);else if(218===e)n=this.lookU16(),t=this.decodeUtf8String(n,2);else if(219===e)n=this.lookU32(),t=this.decodeUtf8String(n,4);else if(220===e){if(0!==(o=this.readU16())){this.pushArrayState(o),this.complete();continue e}t=[]}else if(221===e){if(0!==(o=this.readU32())){this.pushArrayState(o),this.complete();continue e}t=[]}else if(222===e){if(0!==(o=this.readU16())){this.pushMapState(o),this.complete();continue e}t={}}else if(223===e){if(0!==(o=this.readU32())){this.pushMapState(o),this.complete();continue e}t={}}else if(196===e){var o=this.lookU8();t=this.decodeBinary(o,1)}else if(197===e)o=this.lookU16(),t=this.decodeBinary(o,2);else if(198===e)o=this.lookU32(),t=this.decodeBinary(o,4);else if(212===e)t=this.decodeExtension(1,0);else if(213===e)t=this.decodeExtension(2,0);else if(214===e)t=this.decodeExtension(4,0);else if(215===e)t=this.decodeExtension(8,0);else if(216===e)t=this.decodeExtension(16,0);else if(199===e)o=this.lookU8(),t=this.decodeExtension(o,1);else if(200===e)o=this.lookU16(),t=this.decodeExtension(o,2);else{if(201!==e)throw new Gn("Unrecognized type byte: ".concat(to(e)));o=this.lookU32(),t=this.decodeExtension(o,4)}this.complete();for(var r=this.stack;r.length>0;){var s=r[r.length-1];if(0===s.type){if(s.array[s.position]=t,s.position++,s.position!==s.size)continue e;r.pop(),t=s.array}else{if(1===s.type){if("string"!=(i=typeof t)&&"number"!==i)throw new Gn("The type of key must be string or number but "+typeof t);if("__proto__"===t)throw new Gn("The key __proto__ is not allowed");s.key=t,s.type=2;continue e}if(s.map[s.key]=t,s.readCount++,s.readCount!==s.size){s.key=null,s.type=1;continue e}r.pop(),t=s.map}}return t}var i},e.prototype.readHeadByte=function(){return-1===this.headByte&&(this.headByte=this.readU8()),this.headByte},e.prototype.complete=function(){this.headByte=-1},e.prototype.readArraySize=function(){var e=this.readHeadByte();switch(e){case 220:return this.readU16();case 221:return this.readU32();default:if(e<160)return e-144;throw new Gn("Unrecognized array type byte: ".concat(to(e)))}},e.prototype.pushMapState=function(e){if(e>this.maxMapLength)throw new Gn("Max length exceeded: map length (".concat(e,") > maxMapLengthLength (").concat(this.maxMapLength,")"));this.stack.push({type:1,size:e,key:null,readCount:0,map:{}})},e.prototype.pushArrayState=function(e){if(e>this.maxArrayLength)throw new Gn("Max length exceeded: array length (".concat(e,") > maxArrayLength (").concat(this.maxArrayLength,")"));this.stack.push({type:0,size:e,array:new Array(e),position:0})},e.prototype.decodeUtf8String=function(e,t){var n;if(e>this.maxStrLength)throw new Gn("Max length exceeded: UTF-8 byte length (".concat(e,") > maxStrLength (").concat(this.maxStrLength,")"));if(this.bytes.byteLengthVn?function(e,t,n){var o=e.subarray(t,t+n);return Jn.decode(o)}(this.bytes,r,e):qn(this.bytes,r,e),this.pos+=t+e,o},e.prototype.stateIsMapKey=function(){return this.stack.length>0&&1===this.stack[this.stack.length-1].type},e.prototype.decodeBinary=function(e,t){if(e>this.maxBinLength)throw new Gn("Max length exceeded: bin length (".concat(e,") > maxBinLength (").concat(this.maxBinLength,")"));if(!this.hasRemaining(e+t))throw lo;var n=this.pos+t,o=this.bytes.subarray(n,n+e);return this.pos+=t+e,o},e.prototype.decodeExtension=function(e,t){if(e>this.maxExtLength)throw new Gn("Max length exceeded: ext length (".concat(e,") > maxExtLength (").concat(this.maxExtLength,")"));var n=this.view.getInt8(this.pos+t),o=this.decodeBinary(e,t+1);return this.extensionCodec.decode(o,n,this.context)},e.prototype.lookU8=function(){return this.view.getUint8(this.pos)},e.prototype.lookU16=function(){return this.view.getUint16(this.pos)},e.prototype.lookU32=function(){return this.view.getUint32(this.pos)},e.prototype.readU8=function(){var e=this.view.getUint8(this.pos);return this.pos++,e},e.prototype.readI8=function(){var e=this.view.getInt8(this.pos);return this.pos++,e},e.prototype.readU16=function(){var e=this.view.getUint16(this.pos);return this.pos+=2,e},e.prototype.readI16=function(){var e=this.view.getInt16(this.pos);return this.pos+=2,e},e.prototype.readU32=function(){var e=this.view.getUint32(this.pos);return this.pos+=4,e},e.prototype.readI32=function(){var e=this.view.getInt32(this.pos);return this.pos+=4,e},e.prototype.readU64=function(){var e,t,n=(e=this.view,t=this.pos,4294967296*e.getUint32(t)+e.getUint32(t+4));return this.pos+=8,n},e.prototype.readI64=function(){var e=Fn(this.view,this.pos);return this.pos+=8,e},e.prototype.readF32=function(){var e=this.view.getFloat32(this.pos);return this.pos+=4,e},e.prototype.readF64=function(){var e=this.view.getFloat64(this.pos);return this.pos+=8,e},e}();class po{static write(e){let t=e.byteLength||e.length;const n=[];do{let e=127&t;t>>=7,t>0&&(e|=128),n.push(e)}while(t>0);t=e.byteLength||e.length;const o=new Uint8Array(n.length+t);return o.set(n,0),o.set(e,n.length),o.buffer}static parse(e){const t=[],n=new Uint8Array(e),o=[0,7,14,21,28];for(let r=0;r7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=r+i+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(r+i,r+i+a):n.subarray(r+i,r+i+a)),r=r+i+a}return t}}const fo=new Uint8Array([145,on.Ping]);class go{constructor(e){this.name="messagepack",this.version=2,this.transferFormat=_n.Binary,this._errorResult=1,this._voidResult=2,this._nonVoidResult=3,e=e||{},this._encoder=new eo(e.extensionCodec,e.context,e.maxDepth,e.initialBufferSize,e.sortKeys,e.forceFloat32,e.ignoreUndefined,e.forceIntegerToFloat),this._decoder=new uo(e.extensionCodec,e.context,e.maxStrLength,e.maxBinLength,e.maxArrayLength,e.maxMapLength,e.maxExtLength)}parseMessages(e,t){if(!(n=e)||"undefined"==typeof ArrayBuffer||!(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer.");var n;null===t&&(t=Mt.instance);const o=po.parse(e),r=[];for(const e of o){const n=this._parseMessage(e,t);n&&r.push(n)}return r}writeMessage(e){switch(e.type){case on.Invocation:return this._writeInvocation(e);case on.StreamInvocation:return this._writeStreamInvocation(e);case on.StreamItem:return this._writeStreamItem(e);case on.Completion:return this._writeCompletion(e);case on.Ping:return po.write(fo);case on.CancelInvocation:return this._writeCancelInvocation(e);case on.Close:return this._writeClose();case on.Ack:return this._writeAck(e);case on.Sequence:return this._writeSequence(e);default:throw new Error("Invalid message type.")}}_parseMessage(e,t){if(0===e.length)throw new Error("Invalid payload.");const n=this._decoder.decode(e);if(0===n.length||!(n instanceof Array))throw new Error("Invalid payload.");const o=n[0];switch(o){case on.Invocation:return this._createInvocationMessage(this._readHeaders(n),n);case on.StreamItem:return this._createStreamItemMessage(this._readHeaders(n),n);case on.Completion:return this._createCompletionMessage(this._readHeaders(n),n);case on.Ping:return this._createPingMessage(n);case on.Close:return this._createCloseMessage(n);case on.Ack:return this._createAckMessage(n);case on.Sequence:return this._createSequenceMessage(n);default:return t.log(Pt.Information,"Unknown message type '"+o+"' ignored."),null}}_createCloseMessage(e){if(e.length<2)throw new Error("Invalid payload for Close message.");return{allowReconnect:e.length>=3?e[2]:void 0,error:e[1],type:on.Close}}_createPingMessage(e){if(e.length<1)throw new Error("Invalid payload for Ping message.");return{type:on.Ping}}_createInvocationMessage(e,t){if(t.length<5)throw new Error("Invalid payload for Invocation message.");const n=t[2];return n?{arguments:t[4],headers:e,invocationId:n,streamIds:[],target:t[3],type:on.Invocation}:{arguments:t[4],headers:e,streamIds:[],target:t[3],type:on.Invocation}}_createStreamItemMessage(e,t){if(t.length<4)throw new Error("Invalid payload for StreamItem message.");return{headers:e,invocationId:t[2],item:t[3],type:on.StreamItem}}_createCompletionMessage(e,t){if(t.length<4)throw new Error("Invalid payload for Completion message.");const n=t[3];if(n!==this._voidResult&&t.length<5)throw new Error("Invalid payload for Completion message.");let o,r;switch(n){case this._errorResult:o=t[4];break;case this._nonVoidResult:r=t[4]}return{error:o,headers:e,invocationId:t[2],result:r,type:on.Completion}}_createAckMessage(e){if(e.length<1)throw new Error("Invalid payload for Ack message.");return{sequenceId:e[1],type:on.Ack}}_createSequenceMessage(e){if(e.length<1)throw new Error("Invalid payload for Sequence message.");return{sequenceId:e[1],type:on.Sequence}}_writeInvocation(e){let t;return t=e.streamIds?this._encoder.encode([on.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments,e.streamIds]):this._encoder.encode([on.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments]),po.write(t.slice())}_writeStreamInvocation(e){let t;return t=e.streamIds?this._encoder.encode([on.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments,e.streamIds]):this._encoder.encode([on.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments]),po.write(t.slice())}_writeStreamItem(e){const t=this._encoder.encode([on.StreamItem,e.headers||{},e.invocationId,e.item]);return po.write(t.slice())}_writeCompletion(e){const t=e.error?this._errorResult:void 0!==e.result?this._nonVoidResult:this._voidResult;let n;switch(t){case this._errorResult:n=this._encoder.encode([on.Completion,e.headers||{},e.invocationId,t,e.error]);break;case this._voidResult:n=this._encoder.encode([on.Completion,e.headers||{},e.invocationId,t]);break;case this._nonVoidResult:n=this._encoder.encode([on.Completion,e.headers||{},e.invocationId,t,e.result])}return po.write(n.slice())}_writeCancelInvocation(e){const t=this._encoder.encode([on.CancelInvocation,e.headers||{},e.invocationId]);return po.write(t.slice())}_writeClose(){const e=this._encoder.encode([on.Close,null]);return po.write(e.slice())}_writeAck(e){const t=this._encoder.encode([on.Ack,e.sequenceId]);return po.write(t.slice())}_writeSequence(e){const t=this._encoder.encode([on.Sequence,e.sequenceId]);return po.write(t.slice())}_readHeaders(e){const t=e[1];if("object"!=typeof t)throw new Error("Invalid headers.");return t}}const mo="function"==typeof TextDecoder?new TextDecoder("utf-8"):null,vo=mo?mo.decode.bind(mo):function(e){let t=0;const n=e.length,o=[],r=[];for(;t65535&&(r-=65536,o.push(r>>>10&1023|55296),r=56320|1023&r),o.push(r)}o.length>1024&&(r.push(String.fromCharCode.apply(null,o)),o.length=0)}return r.push(String.fromCharCode.apply(null,o)),r.join("")},yo=Math.pow(2,32),wo=Math.pow(2,21)-1;function bo(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24}function _o(e,t){return e[t]+(e[t+1]<<8)+(e[t+2]<<16)+(e[t+3]<<24>>>0)}function Eo(e,t){const n=_o(e,t+4);if(n>wo)throw new Error(`Cannot read uint64 with high order part ${n}, because the result would exceed Number.MAX_SAFE_INTEGER.`);return n*yo+_o(e,t)}class So{constructor(e){this.batchData=e;const t=new To(e);this.arrayRangeReader=new Do(e),this.arrayBuilderSegmentReader=new Ro(e),this.diffReader=new Co(e),this.editReader=new Io(e,t),this.frameReader=new ko(e,t)}updatedComponents(){return bo(this.batchData,this.batchData.length-20)}referenceFrames(){return bo(this.batchData,this.batchData.length-16)}disposedComponentIds(){return bo(this.batchData,this.batchData.length-12)}disposedEventHandlerIds(){return bo(this.batchData,this.batchData.length-8)}updatedComponentsEntry(e,t){const n=e+4*t;return bo(this.batchData,n)}referenceFramesEntry(e,t){return e+20*t}disposedComponentIdsEntry(e,t){const n=e+4*t;return bo(this.batchData,n)}disposedEventHandlerIdsEntry(e,t){const n=e+8*t;return Eo(this.batchData,n)}}class Co{constructor(e){this.batchDataUint8=e}componentId(e){return bo(this.batchDataUint8,e)}edits(e){return e+4}editsEntry(e,t){return e+16*t}}class Io{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}editType(e){return bo(this.batchDataUint8,e)}siblingIndex(e){return bo(this.batchDataUint8,e+4)}newTreeIndex(e){return bo(this.batchDataUint8,e+8)}moveToSiblingIndex(e){return bo(this.batchDataUint8,e+8)}removedAttributeName(e){const t=bo(this.batchDataUint8,e+12);return this.stringReader.readString(t)}}class ko{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}frameType(e){return bo(this.batchDataUint8,e)}subtreeLength(e){return bo(this.batchDataUint8,e+4)}elementReferenceCaptureId(e){const t=bo(this.batchDataUint8,e+4);return this.stringReader.readString(t)}componentId(e){return bo(this.batchDataUint8,e+8)}elementName(e){const t=bo(this.batchDataUint8,e+8);return this.stringReader.readString(t)}textContent(e){const t=bo(this.batchDataUint8,e+4);return this.stringReader.readString(t)}markupContent(e){const t=bo(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeName(e){const t=bo(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeValue(e){const t=bo(this.batchDataUint8,e+8);return this.stringReader.readString(t)}attributeEventHandlerId(e){return Eo(this.batchDataUint8,e+12)}}class To{constructor(e){this.batchDataUint8=e,this.stringTableStartIndex=bo(e,e.length-4)}readString(e){if(-1===e)return null;{const n=bo(this.batchDataUint8,this.stringTableStartIndex+4*e),o=function(e,t){let n=0,o=0;for(let r=0;r<4;r++){const s=e[t+r];if(n|=(127&s)<this.nextBatchId)return this.fatalError?(this.logger.log(ft.Debug,`Received a new batch ${e} but errored out on a previous batch ${this.nextBatchId-1}`),void await n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())):void this.logger.log(ft.Debug,`Waiting for batch ${this.nextBatchId}. Batch ${e} not processed.`);try{this.nextBatchId++,this.logger.log(ft.Debug,`Applying batch ${e}.`),Ce(Nn.Server,new So(t)),await this.completeBatch(n,e)}catch(t){throw this.fatalError=t.toString(),this.logger.log(ft.Error,`There was an error applying batch ${e}.`),n.send("OnRenderCompleted",e,t.toString()),t}}getLastBatchid(){return this.nextBatchId-1}async completeBatch(e,t){try{await e.send("OnRenderCompleted",t,null)}catch{this.logger.log(ft.Warning,`Failed to deliver completion notification for render '${t}'.`)}}}let Ao=!1;function No(){const e=document.querySelector("#blazor-error-ui");e&&(e.style.display="block"),Ao||(Ao=!0,document.querySelectorAll("#blazor-error-ui .reload").forEach((e=>{e.onclick=function(e){location.reload(),e.preventDefault()}})),document.querySelectorAll("#blazor-error-ui .dismiss").forEach((e=>{e.onclick=function(e){const t=document.querySelector("#blazor-error-ui");t&&(t.style.display="none"),e.preventDefault()}})))}class Po{constructor(t,n,o,r){this._firstUpdate=!0,this._renderingFailed=!1,this._disposed=!1,this._circuitId=void 0,this._applicationState=n,this._componentManager=t,this._options=o,this._logger=r,this._renderQueue=new xo(this._logger),this._dispatcher=e.attachDispatcher(this)}start(){if(this.isDisposedOrDisposing())throw new Error("Cannot start a disposed circuit.");return this._startPromise||(this._startPromise=this.startCore()),this._startPromise}updateRootComponents(e){var t,n;return this._firstUpdate?(this._firstUpdate=!1,null===(t=this._connection)||void 0===t?void 0:t.send("UpdateRootComponents",e,this._applicationState)):null===(n=this._connection)||void 0===n?void 0:n.send("UpdateRootComponents",e,"")}async startCore(){if(this._connection=await this.startConnection(),this._connection.state!==rn.Connected)return!1;const e=JSON.stringify(this._componentManager.initialComponents.map((e=>xt(e))));return this._circuitId=await this._connection.invoke("StartCircuit",We.getBaseURI(),We.getLocationHref(),e,this._applicationState||""),!!this._circuitId}async startConnection(){var e,t;const n=new go;n.name="blazorpack";const o=(new An).withUrl("_blazor").withHubProtocol(n);this._options.configureSignalR(o);const r=o.build();r.on("JS.AttachComponent",((e,t)=>Ee(Nn.Server,this.resolveElement(t,e),e,!1))),r.on("JS.BeginInvokeJS",this._dispatcher.beginInvokeJSFromDotNet.bind(this._dispatcher)),r.on("JS.EndInvokeDotNet",this._dispatcher.endInvokeDotNetFromJS.bind(this._dispatcher)),r.on("JS.ReceiveByteArray",this._dispatcher.receiveByteArray.bind(this._dispatcher)),r.on("JS.BeginTransmitStream",(e=>{const t=new ReadableStream({start:t=>{r.stream("SendDotNetStreamToJS",e).subscribe({next:e=>t.enqueue(e),complete:()=>t.close(),error:e=>t.error(e)})}});this._dispatcher.supplyDotNetStream(e,t)})),r.on("JS.RenderBatch",(async(e,t)=>{var n,o;this._logger.log(Pt.Debug,`Received render batch with id ${e} and ${t.byteLength} bytes.`),await this._renderQueue.processBatch(e,t,this._connection),null===(o=(n=this._componentManager).onAfterRenderBatch)||void 0===o||o.call(n,Nn.Server)})),r.on("JS.EndLocationChanging",pt._internal.navigationManager.endLocationChanging),r.onclose((e=>!this._disposed&&!this._renderingFailed&&this._options.reconnectionHandler.onConnectionDown(this._options.reconnectionOptions,e))),r.on("JS.Error",(e=>{this._renderingFailed=!0,this.unhandledError(e),No()}));try{await r.start()}catch(e){if(this.unhandledError(e),"FailedToNegotiateWithServerError"===e.errorType)throw e;No(),e.innerErrors&&(e.innerErrors.some((e=>"UnsupportedTransportError"===e.errorType&&e.transport===bn.WebSockets))?this._logger.log(Pt.Error,"Unable to connect, please ensure you are using an updated browser that supports WebSockets."):e.innerErrors.some((e=>"FailedToStartTransportError"===e.errorType&&e.transport===bn.WebSockets))?this._logger.log(Pt.Error,"Unable to connect, please ensure WebSockets are available. A VPN or proxy may be blocking the connection."):e.innerErrors.some((e=>"DisabledTransportError"===e.errorType&&e.transport===bn.LongPolling))&&this._logger.log(Pt.Error,"Unable to initiate a SignalR connection to the server. This might be because the server is not configured to support WebSockets. For additional details, visit https://aka.ms/blazor-server-websockets-error."))}return(null===(t=null===(e=r.connection)||void 0===e?void 0:e.features)||void 0===t?void 0:t.inherentKeepAlive)&&this._logger.log(Pt.Warning,"Failed to connect via WebSockets, using the Long Polling fallback transport. This may be due to a VPN or proxy blocking the connection. To troubleshoot this, visit https://aka.ms/blazor-server-using-fallback-long-polling."),r}async disconnect(){var e;await(null===(e=this._connection)||void 0===e?void 0:e.stop())}async reconnect(){if(!this._circuitId)throw new Error("Circuit host not initialized.");return this._connection.state===rn.Connected||(this._connection=await this.startConnection(),!!await this._connection.invoke("ConnectCircuit",this._circuitId)&&(this._options.reconnectionHandler.onConnectionUp(),!0))}beginInvokeDotNetFromJS(e,t,n,o,r){this.throwIfDispatchingWhenDisposed(),this._connection.send("BeginInvokeDotNetFromJS",e?e.toString():null,t,n,o||0,r)}endInvokeJSFromDotNet(e,t,n){this.throwIfDispatchingWhenDisposed(),this._connection.send("EndInvokeJSFromDotNet",e,t,n)}sendByteArray(e,t){this.throwIfDispatchingWhenDisposed(),this._connection.send("ReceiveByteArray",e,t)}throwIfDispatchingWhenDisposed(){if(this._disposed)throw new Error("The circuit associated with this dispatcher is no longer available.")}sendLocationChanged(e,t,n){return this._connection.send("OnLocationChanged",e,t,n)}sendLocationChanging(e,t,n,o){return this._connection.send("OnLocationChanging",e,t,n,o)}sendJsDataStream(e,t,n){return function(e,t,n,o){setTimeout((async()=>{let r=5,s=(new Date).valueOf();try{const i=t instanceof Blob?t.size:t.byteLength;let a=0,c=0;for(;a1)await e.send("ReceiveJSDataChunk",n,c,h,null);else{if(!await e.invoke("ReceiveJSDataChunk",n,c,h,null))break;const t=(new Date).valueOf(),o=t-s;s=t,r=Math.max(1,Math.round(500/Math.max(1,o)))}a+=l,c++}}catch(t){await e.send("ReceiveJSDataChunk",n,-1,null,t.toString())}}),0)}(this._connection,e,t,n)}resolveElement(e,t){const n=w(e);if(n)return H(n,!0);const o=Number.parseInt(e);if(!Number.isNaN(o))return $(this._componentManager.resolveRootComponent(o,t));throw new Error(`Invalid sequence number or identifier '${e}'.`)}getRootComponentManager(){return this._componentManager}unhandledError(e){this._logger.log(Pt.Error,e),this.disconnect()}getDisconnectFormData(){const e=new FormData,t=this._circuitId;return e.append("circuitId",t),e}didRenderingFail(){return this._renderingFailed}isDisposedOrDisposing(){return void 0!==this._disposePromise}sendDisconnectBeacon(){if(this._disposed)return;const e=this.getDisconnectFormData();this._disposed=navigator.sendBeacon("_blazor/disconnect",e)}dispose(){return this._disposePromise||(this._disposePromise=this.disposeCore()),this._disposePromise}async disposeCore(){var e;if(!this._startPromise)return void(this._disposed=!0);await this._startPromise,this._disposed=!0,null===(e=this._connection)||void 0===e||e.stop();const t=this.getDisconnectFormData();fetch("_blazor/disconnect",{method:"POST",body:t}),function(e){if(!S.delete(e))throw new Error(`Interop methods are not registered for renderer ${e}`)}(Nn.Server)}}const Mo={configureSignalR:e=>{},logLevel:ft.Warning,reconnectionOptions:{maxRetries:8,retryIntervalMilliseconds:2e4,dialogId:"components-reconnect-modal"}};class Uo{constructor(e,t,n,o){this.maxRetries=t,this.document=n,this.logger=o,this.addedToDom=!1,this.modal=this.document.createElement("div"),this.modal.id=e,this.maxRetries=t,this.modal.style.cssText=["position: fixed","top: 0","right: 0","bottom: 0","left: 0","z-index: 1050","display: none","overflow: hidden","background-color: #fff","opacity: 0.8","text-align: center","font-weight: bold","transition: visibility 0s linear 500ms"].join(";"),this.message=this.document.createElement("h5"),this.message.style.cssText="margin-top: 20px",this.button=this.document.createElement("button"),this.button.style.cssText="margin:5px auto 5px",this.button.textContent="Retry";const r=this.document.createElement("a");r.addEventListener("click",(()=>location.reload())),r.textContent="reload",this.reloadParagraph=this.document.createElement("p"),this.reloadParagraph.textContent="Alternatively, ",this.reloadParagraph.appendChild(r),this.modal.appendChild(this.message),this.modal.appendChild(this.button),this.modal.appendChild(this.reloadParagraph),this.loader=this.getLoader(),this.message.after(this.loader),this.button.addEventListener("click",(async()=>{this.show();try{await pt.reconnect()||this.rejected()}catch(e){this.logger.log(ft.Error,e),this.failed()}}))}show(){this.addedToDom||(this.addedToDom=!0,this.document.body.appendChild(this.modal)),this.modal.style.display="block",this.loader.style.display="inline-block",this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.textContent="Attempting to reconnect to the server...",this.modal.style.visibility="hidden",setTimeout((()=>{this.modal.style.visibility="visible"}),0)}update(e){this.message.textContent=`Attempting to reconnect to the server: ${e} of ${this.maxRetries}`}hide(){this.modal.style.display="none"}failed(){this.button.style.display="block",this.reloadParagraph.style.display="none",this.loader.style.display="none";const e=this.document.createTextNode("Reconnection failed. Try "),t=this.document.createElement("a");t.textContent="reloading",t.setAttribute("href",""),t.addEventListener("click",(()=>location.reload()));const n=this.document.createTextNode(" the page if you're unable to reconnect.");this.message.replaceChildren(e,t,n)}rejected(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.loader.style.display="none";const e=this.document.createTextNode("Could not reconnect to the server. "),t=this.document.createElement("a");t.textContent="Reload",t.setAttribute("href",""),t.addEventListener("click",(()=>location.reload()));const n=this.document.createTextNode(" the page to restore functionality.");this.message.replaceChildren(e,t,n)}getLoader(){const e=this.document.createElement("div");return e.style.cssText=["border: 0.3em solid #f3f3f3","border-top: 0.3em solid #3498db","border-radius: 50%","width: 2em","height: 2em","display: inline-block"].join(";"),e.animate([{transform:"rotate(0deg)"},{transform:"rotate(360deg)"}],{duration:2e3,iterations:1/0}),e}}class Lo{constructor(e,t,n){this.dialog=e,this.maxRetries=t,this.document=n,this.document=n;const o=this.document.getElementById(Lo.MaxRetriesId);o&&(o.innerText=this.maxRetries.toString())}show(){this.removeClasses(),this.dialog.classList.add(Lo.ShowClassName)}update(e){const t=this.document.getElementById(Lo.CurrentAttemptId);t&&(t.innerText=e.toString())}hide(){this.removeClasses(),this.dialog.classList.add(Lo.HideClassName)}failed(){this.removeClasses(),this.dialog.classList.add(Lo.FailedClassName)}rejected(){this.removeClasses(),this.dialog.classList.add(Lo.RejectedClassName)}removeClasses(){this.dialog.classList.remove(Lo.ShowClassName,Lo.HideClassName,Lo.FailedClassName,Lo.RejectedClassName)}}Lo.ShowClassName="components-reconnect-show",Lo.HideClassName="components-reconnect-hide",Lo.FailedClassName="components-reconnect-failed",Lo.RejectedClassName="components-reconnect-rejected",Lo.MaxRetriesId="components-reconnect-max-retries",Lo.CurrentAttemptId="components-reconnect-current-attempt";class Bo{constructor(e,t,n){this._currentReconnectionProcess=null,this._logger=e,this._reconnectionDisplay=t,this._reconnectCallback=n||pt.reconnect}onConnectionDown(e,t){if(!this._reconnectionDisplay){const t=document.getElementById(e.dialogId);this._reconnectionDisplay=t?new Lo(t,e.maxRetries,document):new Uo(e.dialogId,e.maxRetries,document,this._logger)}this._currentReconnectionProcess||(this._currentReconnectionProcess=new Fo(e,this._logger,this._reconnectCallback,this._reconnectionDisplay))}onConnectionUp(){this._currentReconnectionProcess&&(this._currentReconnectionProcess.dispose(),this._currentReconnectionProcess=null)}}class Fo{constructor(e,t,n,o){this.logger=t,this.reconnectCallback=n,this.isDisposed=!1,this.reconnectDisplay=o,this.reconnectDisplay.show(),this.attemptPeriodicReconnection(e)}dispose(){this.isDisposed=!0,this.reconnectDisplay.hide()}async attemptPeriodicReconnection(e){for(let t=0;tFo.MaximumFirstRetryInterval?Fo.MaximumFirstRetryInterval:e.retryIntervalMilliseconds;if(await this.delay(n),this.isDisposed)break;try{return await this.reconnectCallback()?void 0:void this.reconnectDisplay.rejected()}catch(e){this.logger.log(ft.Error,e)}}this.reconnectDisplay.failed()}delay(e){return new Promise((t=>setTimeout(t,e)))}}Fo.MaximumFirstRetryInterval=3e3;class Oo{constructor(){this.afterStartedCallbacks=[]}async importInitializersAsync(e,t){await Promise.all(e.map((e=>async function(e,n){const o=function(e){const t=document.baseURI;return t.endsWith("/")?`${t}${e}`:`${t}/${e}`}(n),r=await import(o);if(void 0===r)return;const{beforeStart:s,afterStarted:i}=r;return i&&e.afterStartedCallbacks.push(i),s?s(...t):void 0}(this,e))))}async invokeAfterStartedCallbacks(e){await k,await Promise.all(this.afterStartedCallbacks.map((t=>t(e))))}}let $o,Ho,jo,Wo,qo,zo=!1;function Jo(e){if(jo)throw new Error("Circuit options have already been configured.");jo=function(e){const t={...Mo,...e};return e&&e.reconnectionOptions&&(t.reconnectionOptions={...Mo.reconnectionOptions,...e.reconnectionOptions}),t}(e)}function Vo(e){return Ho.updateRootComponents(e)}function Ko(e){return qo=e,qo}var Xo,Go;const Yo=navigator,Qo=Yo.userAgentData&&Yo.userAgentData.brands,Zo=Qo&&Qo.length>0?Qo.some((e=>"Google Chrome"===e.brand||"Microsoft Edge"===e.brand||"Chromium"===e.brand)):window.chrome,er=null!==(Go=null===(Xo=Yo.userAgentData)||void 0===Xo?void 0:Xo.platform)&&void 0!==Go?Go:navigator.platform;function tr(e){return 0!==e.debugLevel&&(Zo||navigator.userAgent.includes("Firefox"))}let nr,or,rr,sr,ir,ar;const cr=Math.pow(2,32),lr=Math.pow(2,21)-1;let hr=null;function dr(e){return or.getI32(e)}const ur={load:function(e,t){return async function(e,t){const{dotnet:n}=await async function(e){if("undefined"==typeof WebAssembly||!WebAssembly.validate)throw new Error("This browser does not support WebAssembly.");let t="_framework/dotnet.js";if(e.loadBootResource){const n="dotnetjs",o=e.loadBootResource(n,"dotnet.js",t,"","js-module-dotnet");if("string"==typeof o)t=o;else if(o)throw new Error(`For a ${n} resource, custom loaders must supply a URI string.`)}const n=new URL(t,document.baseURI).toString();return await import(n)}(e),o=function(e,t){const n={maxParallelDownloads:1e6,enableDownloadRetry:!1,applicationEnvironment:e.environment},o={...window.Module||{},onConfigLoaded:async(n,{invokeLibraryInitializers:o})=>{var r,s;n.environmentVariables||(n.environmentVariables={}),"sharded"===n.globalizationMode&&(n.environmentVariables.__BLAZOR_SHARDED_ICU="1"),pt._internal.getApplicationEnvironment=()=>n.applicationEnvironment,null==t||t(n);const i=[e,null!==(s=null===(r=n.resources)||void 0===r?void 0:r.extensions)&&void 0!==s?s:{}];await o("beforeStart",i)},onDownloadResourceProgress:pr,config:n,disableDotnet6Compatibility:!1,out:gr,err:mr};return o}(e,t);e.applicationCulture&&n.withApplicationCulture(e.applicationCulture),e.environment&&n.withApplicationEnvironment(e.environment),e.loadBootResource&&n.withResourceLoader(e.loadBootResource),n.withModuleConfig(o),e.configureRuntime&&e.configureRuntime(n),ar=await n.create()}(e,t)},start:function(){return async function(){if(!ar)throw new Error("The runtime must be loaded it gets configured.");const{MONO:t,BINDING:n,Module:o,setModuleImports:r,INTERNAL:s,getConfig:i,invokeLibraryInitializers:a}=ar;rr=o,nr=n,or=t,ir=s,function(e){const t=er.match(/^Mac/i)?"Cmd":"Alt";tr(e)&&console.info(`Debugging hotkey: Shift+${t}+D (when application has focus)`),document.addEventListener("keydown",(t=>{t.shiftKey&&(t.metaKey||t.altKey)&&"KeyD"===t.code&&(tr(e)?navigator.userAgent.includes("Firefox")?async function(){const e=await fetch(`_framework/debug?url=${encodeURIComponent(location.href)}&isFirefox=true`);200!==e.status&&console.warn(await e.text())}():Zo?function(){const e=document.createElement("a");e.href=`_framework/debug?url=${encodeURIComponent(location.href)}`,e.target="_blank",e.rel="noopener noreferrer",e.click()}():console.error("Currently, only Microsoft Edge (80+), Google Chrome, or Chromium, are supported for debugging."):console.error("Cannot start debugging, because the application was not compiled with debugging enabled."))}))}(i()),pt.runtime=ar,pt._internal.dotNetCriticalError=mr,r("blazor-internal",{Blazor:{_internal:pt._internal}});const c=await ar.getAssemblyExports("Microsoft.AspNetCore.Components.WebAssembly");return Object.assign(pt._internal,{dotNetExports:{...c.Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime}}),sr=e.attachDispatcher({beginInvokeDotNetFromJS:(e,t,n,o,r)=>{if(yr(),!o&&!t)throw new Error("Either assemblyName or dotNetObjectId must have a non null value.");const s=o?o.toString():t;pt._internal.dotNetExports.BeginInvokeDotNet(e?e.toString():null,s,n,r)},endInvokeJSFromDotNet:(e,t,n)=>{pt._internal.dotNetExports.EndInvokeJS(n)},sendByteArray:(e,t)=>{pt._internal.dotNetExports.ReceiveByteArrayFromJS(e,t)},invokeDotNetFromJS:(e,t,n,o)=>(yr(),pt._internal.dotNetExports.InvokeDotNet(e||null,t,null!=n?n:0,o))}),{invokeLibraryInitializers:a}}()},callEntryPoint:async function(){try{await ar.runMain(ar.getConfig().mainAssemblyName,[])}catch(e){console.error(e),No()}},toUint8Array:function(e){const t=vr(e),n=dr(t),o=new Uint8Array(n);return o.set(rr.HEAPU8.subarray(t+4,t+4+n)),o},getArrayLength:function(e){return dr(vr(e))},getArrayEntryPtr:function(e,t,n){return vr(e)+4+t*n},getObjectFieldsBaseAddress:function(e){return e+8},readInt16Field:function(e,t){return n=e+(t||0),or.getI16(n);var n},readInt32Field:function(e,t){return dr(e+(t||0))},readUint64Field:function(e,t){return function(e){const t=e>>2,n=rr.HEAPU32[t+1];if(n>lr)throw new Error(`Cannot read uint64 with high order part ${n}, because the result would exceed Number.MAX_SAFE_INTEGER.`);return n*cr+rr.HEAPU32[t]}(e+(t||0))},readFloatField:function(e,t){return n=e+(t||0),or.getF32(n);var n},readObjectField:function(e,t){return dr(e+(t||0))},readStringField:function(e,t,n){const o=dr(e+(t||0));if(0===o)return null;if(n){const e=nr.unbox_mono_obj(o);return"boolean"==typeof e?e?"":null:e}return nr.conv_string(o)},readStructField:function(e,t){return e+(t||0)},beginHeapLock:function(){return yr(),hr=wr.create(),hr},invokeWhenHeapUnlocked:function(e){hr?hr.enqueuePostReleaseAction(e):e()}};function pr(e,t){const n=e/t*100;document.documentElement.style.setProperty("--blazor-load-percentage",`${n}%`),document.documentElement.style.setProperty("--blazor-load-percentage-text",`"${Math.floor(n)}%"`)}const fr=["DEBUGGING ENABLED"],gr=e=>fr.indexOf(e)<0&&console.log(e),mr=e=>{console.error(e||"(null)"),No()};function vr(e){return e+12}function yr(){if(hr)throw new Error("Assertion failed - heap is currently locked")}class wr{enqueuePostReleaseAction(e){this.postReleaseActions||(this.postReleaseActions=[]),this.postReleaseActions.push(e)}release(){var e;if(hr!==this)throw new Error("Trying to release a lock which isn't current");for(ir.mono_wasm_gc_unlock(),hr=null;null===(e=this.postReleaseActions)||void 0===e?void 0:e.length;)this.postReleaseActions.shift()(),yr()}static create(){return ir.mono_wasm_gc_lock(),new wr}}class br{constructor(e){this.batchAddress=e,this.arrayRangeReader=_r,this.arrayBuilderSegmentReader=Er,this.diffReader=Sr,this.editReader=Cr,this.frameReader=Ir}updatedComponents(){return qo.readStructField(this.batchAddress,0)}referenceFrames(){return qo.readStructField(this.batchAddress,_r.structLength)}disposedComponentIds(){return qo.readStructField(this.batchAddress,2*_r.structLength)}disposedEventHandlerIds(){return qo.readStructField(this.batchAddress,3*_r.structLength)}updatedComponentsEntry(e,t){return kr(e,t,Sr.structLength)}referenceFramesEntry(e,t){return kr(e,t,Ir.structLength)}disposedComponentIdsEntry(e,t){const n=kr(e,t,4);return qo.readInt32Field(n)}disposedEventHandlerIdsEntry(e,t){const n=kr(e,t,8);return qo.readUint64Field(n)}}const _r={structLength:8,values:e=>qo.readObjectField(e,0),count:e=>qo.readInt32Field(e,4)},Er={structLength:12,values:e=>{const t=qo.readObjectField(e,0),n=qo.getObjectFieldsBaseAddress(t);return qo.readObjectField(n,0)},offset:e=>qo.readInt32Field(e,4),count:e=>qo.readInt32Field(e,8)},Sr={structLength:4+Er.structLength,componentId:e=>qo.readInt32Field(e,0),edits:e=>qo.readStructField(e,4),editsEntry:(e,t)=>kr(e,t,Cr.structLength)},Cr={structLength:20,editType:e=>qo.readInt32Field(e,0),siblingIndex:e=>qo.readInt32Field(e,4),newTreeIndex:e=>qo.readInt32Field(e,8),moveToSiblingIndex:e=>qo.readInt32Field(e,8),removedAttributeName:e=>qo.readStringField(e,16)},Ir={structLength:36,frameType:e=>qo.readInt16Field(e,4),subtreeLength:e=>qo.readInt32Field(e,8),elementReferenceCaptureId:e=>qo.readStringField(e,16),componentId:e=>qo.readInt32Field(e,12),elementName:e=>qo.readStringField(e,16),textContent:e=>qo.readStringField(e,16),markupContent:e=>qo.readStringField(e,16),attributeName:e=>qo.readStringField(e,16),attributeValue:e=>qo.readStringField(e,24,!0),attributeEventHandlerId:e=>qo.readUint64Field(e,8)};function kr(e,t,n){return qo.getArrayEntryPtr(e,t,n)}class Tr{constructor(e){this.componentManager=e}resolveRegisteredElement(e,t){const n=Number.parseInt(e);if(!Number.isNaN(n))return $(this.componentManager.resolveRootComponent(n,t))}getParameterValues(e){return this.componentManager.initialComponents[e].parameterValues}getParameterDefinitions(e){return this.componentManager.initialComponents[e].parameterDefinitions}getTypeName(e){return this.componentManager.initialComponents[e].typeName}getAssembly(e){return this.componentManager.initialComponents[e].assembly}getCount(){return this.componentManager.initialComponents.length}}let Dr,Rr,xr,Ar=!1,Nr=!1,Pr=!0,Mr=!1;const Ur=new Promise((e=>{xr=e}));let Lr;const Br=new Promise((e=>{Lr=e}));function Fr(){return null!=Rr||(Rr=(async()=>{const e=null!=Dr?Dr:{},t=null==Dr?void 0:Dr.configureRuntime;e.configureRuntime=e=>{null==t||t(e),Mr&&e.withEnvironmentVariable("__BLAZOR_WEBASSEMBLY_WAIT_FOR_ROOT_COMPONENTS","true")},await ur.load(e,xr),Ar=!0})()),Rr}function Or(){return Ar}function $r(t,n,o,r){const s=ur.readStringField(t,0),i=ur.readInt32Field(t,4),a=ur.readStringField(t,8),c=ur.readUint64Field(t,20);if(null!==a){const e=ur.readUint64Field(t,12);if(0!==e)return sr.beginInvokeJSFromDotNet(e,s,a,i,c),0;{const e=sr.invokeJSFromDotNet(s,a,i,c);return null===e?0:nr.js_string_to_mono_string(e)}}{const t=e.findJSFunction(s,c).call(null,n,o,r);switch(i){case e.JSCallResultType.Default:return t;case e.JSCallResultType.JSObjectReference:return e.createJSObjectReference(t).__jsObjectId;case e.JSCallResultType.JSStreamReference:{const n=e.createJSStreamReference(t),o=JSON.stringify(n);return nr.js_string_to_mono_string(o)}case e.JSCallResultType.JSVoidResult:return null;default:throw new Error(`Invalid JS call result type '${i}'.`)}}}function Hr(e,t,n,o,r){return 0!==r?(sr.beginInvokeJSFromDotNet(r,e,o,n,t),null):sr.invokeJSFromDotNet(e,o,n,t)}function jr(e,t,n){sr.endInvokeDotNetFromJS(e,t,n)}function Wr(e,t,n,o){!function(e,t,n,o,r){let s=ut.get(t);if(!s){const n=new ReadableStream({start(e){ut.set(t,e),s=e}});e.supplyDotNetStream(t,n)}r?(s.error(r),ut.delete(t)):0===o?(s.close(),ut.delete(t)):s.enqueue(n.length===o?n:n.subarray(0,o))}(sr,e,t,n,o)}function qr(e,t){sr.receiveByteArray(e,t)}function zr(e,t){t.namespaceURI?e.setAttributeNS(t.namespaceURI,t.name,t.value):e.setAttribute(t.name,t.value)}const Jr="data-permanent";var Vr,Kr;!function(e){e[e.None=0]="None",e[e.Some=1]="Some",e[e.Infinite=2]="Infinite"}(Vr||(Vr={})),function(e){e.Keep="keep",e.Update="update",e.Insert="insert",e.Delete="delete"}(Kr||(Kr={}));class Xr{static create(e,t,n){return 0===t&&n===e.length?e:new Xr(e,t,n)}constructor(e,t,n){this.source=e,this.startIndex=t,this.length=n}item(e){return this.source.item(e+this.startIndex)}forEach(e,t){for(let t=0;t=n&&i>=o&&r(e.item(s),t.item(i))===Vr.None;)s--,i--,a++;return a}(e,t,o,o,n),s=function(e){var t;const n=[];let o=e.length-1,r=(null===(t=e[o])||void 0===t?void 0:t.length)-1;for(;o>0||r>0;){const t=0===o?Kr.Insert:0===r?Kr.Delete:e[o][r];switch(n.unshift(t),t){case Kr.Keep:case Kr.Update:o--,r--;break;case Kr.Insert:r--;break;case Kr.Delete:o--}}return n}(function(e,t,n){const o=[],r=[],s=e.length,i=t.length;if(0===s&&0===i)return[];for(let e=0;e<=s;e++)(o[e]=Array(i+1))[0]=e,r[e]=Array(i+1);const a=o[0];for(let e=1;e<=i;e++)a[e]=e;for(let a=1;a<=s;a++)for(let s=1;s<=i;s++){const i=n(e.item(a-1),t.item(s-1)),c=o[a-1][s]+1,l=o[a][s-1]+1;let h;switch(i){case Vr.None:h=o[a-1][s-1];break;case Vr.Some:h=o[a-1][s-1]+1;break;case Vr.Infinite:h=Number.MAX_VALUE}h{history.pushState(null,"",e),ms(e)}))}function fs(e){Me()||ms(location.href)}function gs(e){if(Me()||e.defaultPrevented)return;const t=e.target;if(t instanceof HTMLFormElement){if(!function(e){const t=e.getAttribute("data-enhance");return"string"==typeof t&&""===t||"true"===(null==t?void 0:t.toLowerCase())}(t))return;e.preventDefault();const n=new URL(t.action),o={method:t.method},r=new FormData(t),s=e.submitter;s&&s.name&&r.append(s.name,s.value),"get"===o.method?n.search=new URLSearchParams(r).toString():o.body=r,ms(n.toString(),o)}}async function ms(e,t){Qr=!0,null==Gr||Gr.abort(),Gr=new AbortController;const n=Gr.signal,o=fetch(e,Object.assign({signal:n,mode:"no-cors",headers:{accept:"text/html;blazor-enhanced-nav=on"}},t));if(await async function(e,t,n,o){let r;try{if(r=await e,!r.body)return void n(r,"");const t=r.headers.get("ssr-framing");if(!t){const e=await r.text();return void n(r,e)}let o=!0;await r.body.pipeThrough(new TextDecoderStream).pipeThrough(function(e){let t="";return new TransformStream({transform(n,o){if(t+=n,t.indexOf(e,t.length-n.length-e.length)>=0){const n=t.split(e);n.slice(0,-1).forEach((e=>o.enqueue(e))),t=n[n.length-1]}},flush(e){e.enqueue(t)}})}(`\x3c!--${t}--\x3e`)).pipeTo(new WritableStream({write(e){o?(o=!1,n(r,e)):(e=>{const t=document.createRange().createContextualFragment(e);for(;t.firstChild;)document.body.appendChild(t.firstChild)})(e)}}))}catch(e){if("AbortError"===e.name&&t.aborted)return;throw e}}(o,n,((n,o)=>{const r=!(null==t?void 0:t.method)||"get"===t.method,s=n.status>=200&&n.status<300;if("opaque"===n.type){if(r)return void ys(e);throw new Error("Enhanced navigation does not support making a non-GET request to an endpoint that redirects to an external origin. Avoid enabling enhanced navigation for form posts that may perform external redirections.")}if(s&&"allow"!==n.headers.get("blazor-enhanced-nav")){if(r)return void ys(e);throw new Error("Enhanced navigation does not support making a non-GET request to a non-Blazor endpoint. Avoid enabling enhanced navigation for forms that post to a non-Blazor endpoint.")}n.redirected&&(r?history.replaceState(null,"",n.url):history.pushState(null,"",n.url),e=n.url);const i=n.headers.get("blazor-enhanced-nav-redirect-location");if(i)return void location.replace(i);const a=n.headers.get("content-type");if((null==a?void 0:a.startsWith("text/html"))&&o){const e=(new DOMParser).parseFromString(o,"text/html");es(document,e),Yr.documentUpdated()}else(null==a?void 0:a.startsWith("text/"))&&o?vs(o):s||o?r?ys(e):vs(`Error: ${t.method} request to ${e} returned non-HTML content of type ${a||"unspecified"}.`):vs(`Error: ${n.status} ${n.statusText}`)})),!n.aborted){const t=e.indexOf("#");if(t>=0){const n=e.substring(t+1),o=document.getElementById(n);null==o||o.scrollIntoView()}Qr=!1,Yr.enhancedNavigationCompleted()}}function vs(e){document.documentElement.textContent=e;const t=document.documentElement.style;t.fontFamily="consolas, monospace",t.whiteSpace="pre-wrap",t.padding="1rem"}function ys(e){history.replaceState(null,"",e+"?"),location.replace(e)}let ws,bs=!0;class _s extends HTMLElement{connectedCallback(){var e;const t=this.parentNode;null===(e=t.parentNode)||void 0===e||e.removeChild(t),t.childNodes.forEach((e=>{if(e instanceof HTMLTemplateElement){const t=e.getAttribute("blazor-component-id");if(t)"true"!==e.getAttribute("enhanced-nav")&&Gr||function(e,t){const n=function(e){const t=`bl:${e}`,n=document.createNodeIterator(document,NodeFilter.SHOW_COMMENT);let o=null;for(;(o=n.nextNode())&&o.textContent!==t;);if(!o)return null;const r=`/bl:${e}`;let s=null;for(;(s=n.nextNode())&&s.textContent!==r;);return s?{startMarker:o,endMarker:s}:null}(e);if(n){const{startMarker:e,endMarker:o}=n;if(bs)es({startExclusive:e,endExclusive:o},t);else{const n=o.parentNode,r=new Range;for(r.setStart(e,e.textContent.length),r.setEnd(o,0),r.deleteContents();t.childNodes[0];)n.insertBefore(t.childNodes[0],o)}ws.documentUpdated()}}(t,e.content);else switch(e.getAttribute("type")){case"redirection":const t=Ne(e.content.textContent),n="form-post"===e.getAttribute("from");"true"===e.getAttribute("enhanced")&&Re(t)?(n?history.pushState(null,"",t):history.replaceState(null,"",t),ms(t)):n?location.assign(t):location.replace(t);break;case"error":vs(e.content.textContent||"Error")}}}))}}class Es{constructor(e){var t;this._circuitInactivityTimeoutMs=e,this._rootComponents=new Set,this._descriptors=new Set,this._pendingComponentsToResolve=new Map,this._didWebAssemblyFailToLoadQuickly=!1,this._isComponentRefreshPending=!1,this.initialComponents=[],t=()=>{this.rootComponentsMayRequireRefresh()},C.push(t)}onAfterRenderBatch(e){e===Nn.Server&&this.circuitMayHaveNoRootComponents()}onDocumentUpdated(){this.rootComponentsMayRequireRefresh()}onEnhancedNavigationCompleted(){this.rootComponentsMayRequireRefresh()}registerComponent(e){this._descriptors.has(e)||("auto"!==e.type&&"webassembly"!==e.type||this.startLoadingWebAssemblyIfNotStarted(),this._descriptors.add(e),this._rootComponents.add({descriptor:e}))}unregisterComponent(e){this._descriptors.delete(e.descriptor),this._rootComponents.delete(e)}async startLoadingWebAssemblyIfNotStarted(){if(void 0!==Rr)return;Mr=!0;const e=Fr();setTimeout((()=>{Or()||this.onWebAssemblyFailedToLoadQuickly()}),pt._internal.loadWebAssemblyQuicklyTimeout);const t=await Ur;(function(e){if(!e.cacheBootResources)return!1;const t=Ss(e);if(!t)return!1;const n=window.localStorage.getItem(t.key);return t.value===n})(t)||this.onWebAssemblyFailedToLoadQuickly(),await e,function(e){const t=Ss(e);t&&window.localStorage.setItem(t.key,t.value)}(t),this.rootComponentsMayRequireRefresh()}onWebAssemblyFailedToLoadQuickly(){this._didWebAssemblyFailToLoadQuickly||(this._didWebAssemblyFailToLoadQuickly=!0,this.rootComponentsMayRequireRefresh())}startCircutIfNotStarted(){return zo?Ho.isDisposedOrDisposing()?function(){if(!zo)throw new Error("Cannot start the circuit until Blazor Server has started.");return Ho.didRenderingFail()?Promise.resolve(!1):(Ho.isDisposedOrDisposing()&&($o=bt(document)||"",Ho=new Po(Ho.getRootComponentManager(),$o,jo,Wo)),Ho.start())}():void 0:async function(e){if(zo)throw new Error("Blazor Server has already started.");zo=!0,$o=bt(document)||"",Wo=new mt(jo.logLevel),Ho=new Po(e,$o,jo,Wo),Wo.log(ft.Information,"Starting up Blazor server-side application."),pt.reconnect=async()=>!(Ho.didRenderingFail()||!await Ho.reconnect()&&(Wo.log(ft.Information,"Reconnection attempt to the circuit was rejected by the server. This may indicate that the associated state is no longer available on the server."),1)),pt.defaultReconnectionHandler=new Bo(Wo),jo.reconnectionHandler=jo.reconnectionHandler||pt.defaultReconnectionHandler,pt._internal.navigationManager.listenForNavigationEvents(((e,t,n)=>Ho.sendLocationChanged(e,t,n)),((e,t,n,o)=>Ho.sendLocationChanging(e,t,n,o))),pt._internal.forceCloseConnection=()=>Ho.disconnect(),pt._internal.sendJSDataStream=(e,t,n)=>Ho.sendJsDataStream(e,t,n);const t=await async function(e){const t=await fetch("_blazor/initializers",{method:"GET",credentials:"include",cache:"no-cache"}),n=await t.json(),o=new Oo;return await o.importInitializersAsync(n,[e]),o}(jo);if(!await Ho.start())return void Wo.log(ft.Error,"Failed to start the circuit.");const n=()=>{Ho.sendDisconnectBeacon()};pt.disconnect=n,window.addEventListener("unload",n,{capture:!1,once:!0}),Wo.log(ft.Information,"Blazor server-side application started."),t.invokeAfterStartedCallbacks(pt)}(this)}async startWebAssemblyIfNotStarted(){this.startLoadingWebAssemblyIfNotStarted(),Nr||await async function(e){if(Nr)throw new Error("Blazor WebAssembly has already started.");Nr=!0,function(){if(window.parent!==window&&!window.opener&&window.frameElement){const e=window.sessionStorage&&window.sessionStorage["Microsoft.AspNetCore.Components.WebAssembly.Authentication.CachedAuthSettings"],t=e&&JSON.parse(e);return t&&t.redirect_uri&&location.href.startsWith(t.redirect_uri)}return!1}()&&await new Promise((()=>{}));const t=Fr();!function(e){const t=R;R=(e,n,o)=>{((e,t,n)=>{const o=Se(e);(null==o?void 0:o.eventDelegator.getHandler(t))&&ur.invokeWhenHeapUnlocked(n)})(e,n,(()=>t(e,n,o)))}}(),pt._internal.applyHotReload=(e,t,n,o)=>{sr.invokeDotNetStaticMethod("Microsoft.AspNetCore.Components.WebAssembly","ApplyHotReloadDelta",e,t,n,o)},pt._internal.getApplyUpdateCapabilities=()=>sr.invokeDotNetStaticMethod("Microsoft.AspNetCore.Components.WebAssembly","GetApplyUpdateCapabilities"),pt._internal.invokeJSFromDotNet=$r,pt._internal.invokeJSJson=Hr,pt._internal.endInvokeDotNetFromJS=jr,pt._internal.receiveWebAssemblyDotNetDataStream=Wr,pt._internal.receiveByteArray=qr;const n=Ko(ur);pt.platform=n,pt._internal.renderBatch=(e,t)=>{const n=ur.beginHeapLock();try{Ce(e,new br(t))}finally{n.release()}},pt._internal.navigationManager.listenForNavigationEvents((async(e,t,n)=>{await sr.invokeDotNetStaticMethodAsync("Microsoft.AspNetCore.Components.WebAssembly","NotifyLocationChanged",e,t,n)}),(async(e,t,n,o)=>{const r=await sr.invokeDotNetStaticMethodAsync("Microsoft.AspNetCore.Components.WebAssembly","NotifyLocationChangingAsync",t,n,o);pt._internal.navigationManager.endLocationChanging(e,r)}));const o=new Tr(e);let r;pt._internal.registeredComponents={getRegisteredComponentsCount:()=>o.getCount(),getAssembly:e=>o.getAssembly(e),getTypeName:e=>o.getTypeName(e),getParameterDefinitions:e=>o.getParameterDefinitions(e)||"",getParameterValues:e=>o.getParameterValues(e)||""},pt._internal.getPersistedState=()=>_t(document,wt)||"",pt._internal.getInitialComponentsUpdate=()=>Br,pt._internal.updateRootComponents=e=>{var t;return null===(t=pt._internal.dotNetExports)||void 0===t?void 0:t.UpdateRootComponentsCore(e)},pt._internal.attachRootComponentToElement=(e,t,n)=>{const r=o.resolveRegisteredElement(e,t);r?Ee(n,r,t,!1):function(e,t,n){const o="::before";let r=!1;if(e.endsWith("::after"))e=e.slice(0,-7),r=!0;else if(e.endsWith(o))throw new Error(`The '${o}' selector is not supported.`);const s=w(e)||document.querySelector(e);if(!s)throw new Error(`Could not find any element matching selector '${e}'.`);Ee(n||0,H(s,!0),t,r)}(e,t,n)};try{await t,r=await n.start()}catch(e){throw new Error(`Failed to start platform. Reason: ${e}`)}n.callEntryPoint(),r.invokeLibraryInitializers("afterStarted",[pt])}(this)}rootComponentsMayRequireRefresh(){this._isComponentRefreshPending||(this._isComponentRefreshPending=!0,setTimeout((()=>{this._isComponentRefreshPending=!1,this.refreshRootComponents(this._rootComponents)}),0))}circuitMayHaveNoRootComponents(){if(this.rendererHasExistingOrPendingComponents(Nn.Server,"server","auto"))return clearTimeout(this._circuitInactivityTimeoutId),void(this._circuitInactivityTimeoutId=void 0);void 0===this._circuitInactivityTimeoutId&&(this._circuitInactivityTimeoutId=setTimeout((()=>{this.rendererHasExistingOrPendingComponents(Nn.Server,"server","auto")||(async function(){await(null==Ho?void 0:Ho.dispose())}(),this._circuitInactivityTimeoutId=void 0)}),this._circuitInactivityTimeoutMs))}rendererHasComponents(e){const t=Se(e);return void 0!==t&&t.getRootComponentCount()>0}rendererHasExistingOrPendingComponents(e,...t){if(this.rendererHasComponents(e))return!0;for(const{descriptor:{type:n},assignedRendererId:o}of this._rootComponents){if(o===e)return!0;if(void 0===o&&-1!==t.indexOf(n))return!0}return!1}refreshRootComponents(e){const t=new Map;for(const n of e){const e=this.determinePendingOperation(n);if(!e)continue;const o=n.assignedRendererId;if(!o)throw new Error("Descriptors must be assigned a renderer ID before getting used as root components");let r=t.get(o);r||(r=[],t.set(o,r)),r.push(e)}for(const[e,n]of t){const t=JSON.stringify(n);e===Nn.Server?Vo(t):this.updateWebAssemblyRootComponents(t)}this.circuitMayHaveNoRootComponents()}updateWebAssemblyRootComponents(e){Pr?(Lr(e),Pr=!1):function(e){if(!Nr)throw new Error("Blazor WebAssembly has not started.");if(!pt._internal.updateRootComponents)throw new Error("Blazor WebAssembly has not initialized.");pt._internal.updateRootComponents(e)}(e)}resolveRendererIdForDescriptor(e){switch("auto"===e.type?this.getAutoRenderMode():e.type){case"server":return this.startCircutIfNotStarted(),Nn.Server;case"webassembly":return this.startWebAssemblyIfNotStarted(),Nn.WebAssembly;case null:return null}}getAutoRenderMode(){return this.rendererHasExistingOrPendingComponents(Nn.WebAssembly,"webassembly")?"webassembly":this.rendererHasExistingOrPendingComponents(Nn.Server,"server")?"server":Or()?"webassembly":this._didWebAssemblyFailToLoadQuickly?"server":null}determinePendingOperation(e){if(n=e.descriptor,document.contains(n.start)){if(void 0===e.assignedRendererId){if(Qr||"loading"===document.readyState)return null;const n=this.resolveRendererIdForDescriptor(e.descriptor);return null===n?null:(t=n,S.has(t)?(e.assignedRendererId=n,e.uniqueIdAtLastUpdate=e.descriptor.uniqueId,this._pendingComponentsToResolve.set(e.descriptor.uniqueId,e),{type:"add",selectorId:e.descriptor.uniqueId,marker:xt(e.descriptor)}):null)}if(e.uniqueIdAtLastUpdate===e.descriptor.uniqueId)return null;if(void 0!==e.interactiveComponentId)return e.uniqueIdAtLastUpdate=e.descriptor.uniqueId,{type:"update",componentId:e.interactiveComponentId,marker:xt(e.descriptor)}}else{if(this.unregisterComponent(e),void 0!==e.assignedRendererId&&void 0!==e.interactiveComponentId){const t=Se(e.assignedRendererId);null==t||t.disposeComponent(e.interactiveComponentId)}if(void 0!==e.interactiveComponentId)return{type:"remove",componentId:e.interactiveComponentId}}var t,n;return null}resolveRootComponent(e,t){const n=this._pendingComponentsToResolve.get(e);if(!n)throw new Error(`Could not resolve a root component for descriptor with ID '${e}'.`);if(this._pendingComponentsToResolve.delete(e),void 0!==n.interactiveComponentId)throw new Error("Cannot resolve a root component for the same descriptor multiple times.");return n.interactiveComponentId=t,this.refreshRootComponents([n]),n.descriptor}}function Ss(e){var t;const n=null===(t=e.resources)||void 0===t?void 0:t.hash,o=e.mainAssemblyName;return n&&o?{key:`blazor-resource-hash:${o}`,value:n}:null}class Cs{constructor(){this._eventListeners=new Map}static create(e){const t=new Cs;return e.addEventListener=t.addEventListener.bind(t),e.removeEventListener=t.removeEventListener.bind(t),t}addEventListener(e,t){let n=this._eventListeners.get(e);n||(n=new Set,this._eventListeners.set(e,n)),n.add(t)}removeEventListener(e,t){var n;null===(n=this._eventListeners.get(e))||void 0===n||n.delete(t)}dispatchEvent(e,t){const n=this._eventListeners.get(e);if(!n)return;const o={...t,type:e};for(const e of n)e(o)}}let Is,ks=!1;function Ts(e){var t,n,o;if(ks)throw new Error("Blazor has already started.");ks=!0,pt._internal.loadWebAssemblyQuicklyTimeout=3e3,pt._internal.hotReloadApplied=()=>{xe()&&Ae(location.href,!0)},Jo(null==e?void 0:e.circuit),function(e){if(Dr)throw new Error("WebAssembly options have already been configured.");Dr=e}(null==e?void 0:e.webAssembly),Is=new Es(null!==(n=null===(t=null==e?void 0:e.ssr)||void 0===t?void 0:t.circuitInactivityTimeoutMs)&&void 0!==n?n:2e3);const r=Cs.create(pt),s={documentUpdated:()=>{Is.onDocumentUpdated(),r.dispatchEvent("enhancedload",{})},enhancedNavigationCompleted(){Is.onEnhancedNavigationCompleted()}};return Zr=Is,function(e,t){ws=t,(null==e?void 0:e.disableDomPreservation)&&(bs=!1),customElements.define("blazor-ssr-end",_s)}(null==e?void 0:e.ssr,s),(null===(o=null==e?void 0:e.ssr)||void 0===o?void 0:o.disableDomPreservation)||ds(s),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",Ds):Ds(),Promise.resolve()}function Ds(){!function(e){const t=is(document);for(const e of t)null==Zr||Zr.registerComponent(e)}(),Is.onDocumentUpdated()}pt.start=Ts,window.DotNet=e,document&&document.currentScript&&"false"!==document.currentScript.getAttribute("autostart")&&Ts()})()})(); \ No newline at end of file +(()=>{var e={778:()=>{},77:()=>{},203:()=>{},200:()=>{},628:()=>{},321:()=>{}},t={};function n(o){var r=t[o];if(void 0!==r)return r.exports;var i=t[o]={exports:{}};return e[o](i,i.exports,n),i.exports}n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),(()=>{"use strict";var e,t,o;!function(e){const t=[],n="__jsObjectId",o="__dotNetObject",r="__byte[]",i="__dotNetStream",s="__jsStreamReferenceLength";let a,c;class l{constructor(e){this._jsObject=e,this._cachedFunctions=new Map}findFunction(e){const t=this._cachedFunctions.get(e);if(t)return t;let n,o=this._jsObject;if(e.split(".").forEach((t=>{if(!(t in o))throw new Error(`Could not find '${e}' ('${t}' was undefined).`);n=o,o=o[t]})),o instanceof Function)return o=o.bind(n),this._cachedFunctions.set(e,o),o;throw new Error(`The value '${e}' is not a function.`)}getWrappedObject(){return this._jsObject}}const h={0:new l(window)};h[0]._cachedFunctions.set("import",(e=>("string"==typeof e&&e.startsWith("./")&&(e=new URL(e.substr(2),document.baseURI).toString()),import(e))));let d,u=1;function p(e){t.push(e)}function f(e){if(e&&"object"==typeof e){h[u]=new l(e);const t={[n]:u};return u++,t}throw new Error(`Cannot create a JSObjectReference from the value '${e}'.`)}function g(e){let t=-1;if(e instanceof ArrayBuffer&&(e=new Uint8Array(e)),e instanceof Blob)t=e.size;else{if(!(e.buffer instanceof ArrayBuffer))throw new Error("Supplied value is not a typed array or blob.");if(void 0===e.byteLength)throw new Error(`Cannot create a JSStreamReference from the value '${e}' as it doesn't have a byteLength.`);t=e.byteLength}const o={[s]:t};try{const t=f(e);o[n]=t[n]}catch(t){throw new Error(`Cannot create a JSStreamReference from the value '${e}'.`)}return o}function m(e,n){c=e;const o=n?JSON.parse(n,((e,n)=>t.reduce(((t,n)=>n(e,t)),n))):null;return c=void 0,o}function v(){if(void 0===a)throw new Error("No call dispatcher has been set.");if(null===a)throw new Error("There are multiple .NET runtimes present, so a default dispatcher could not be resolved. Use DotNetObject to invoke .NET instance methods.");return a}e.attachDispatcher=function(e){const t=new y(e);return void 0===a?a=t:a&&(a=null),t},e.attachReviver=p,e.invokeMethod=function(e,t,...n){return v().invokeDotNetStaticMethod(e,t,...n)},e.invokeMethodAsync=function(e,t,...n){return v().invokeDotNetStaticMethodAsync(e,t,...n)},e.createJSObjectReference=f,e.createJSStreamReference=g,e.disposeJSObjectReference=function(e){const t=e&&e[n];"number"==typeof t&&_(t)},function(e){e[e.Default=0]="Default",e[e.JSObjectReference=1]="JSObjectReference",e[e.JSStreamReference=2]="JSStreamReference",e[e.JSVoidResult=3]="JSVoidResult"}(d=e.JSCallResultType||(e.JSCallResultType={}));class y{constructor(e){this._dotNetCallDispatcher=e,this._byteArraysToBeRevived=new Map,this._pendingDotNetToJSStreams=new Map,this._pendingAsyncCalls={},this._nextAsyncCallId=1}getDotNetCallDispatcher(){return this._dotNetCallDispatcher}invokeJSFromDotNet(e,t,n,o){const r=m(this,t),i=I(b(e,o)(...r||[]),n);return null==i?null:T(this,i)}beginInvokeJSFromDotNet(e,t,n,o,r){const i=new Promise((e=>{const o=m(this,n);e(b(t,r)(...o||[]))}));e&&i.then((t=>T(this,[e,!0,I(t,o)]))).then((t=>this._dotNetCallDispatcher.endInvokeJSFromDotNet(e,!0,t)),(t=>this._dotNetCallDispatcher.endInvokeJSFromDotNet(e,!1,JSON.stringify([e,!1,w(t)]))))}endInvokeDotNetFromJS(e,t,n){const o=t?m(this,n):new Error(n);this.completePendingCall(parseInt(e,10),t,o)}invokeDotNetStaticMethod(e,t,...n){return this.invokeDotNetMethod(e,t,null,n)}invokeDotNetStaticMethodAsync(e,t,...n){return this.invokeDotNetMethodAsync(e,t,null,n)}invokeDotNetMethod(e,t,n,o){if(this._dotNetCallDispatcher.invokeDotNetFromJS){const r=T(this,o),i=this._dotNetCallDispatcher.invokeDotNetFromJS(e,t,n,r);return i?m(this,i):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeDotNetMethodAsync instead.")}invokeDotNetMethodAsync(e,t,n,o){if(e&&n)throw new Error(`For instance method calls, assemblyName should be null. Received '${e}'.`);const r=this._nextAsyncCallId++,i=new Promise(((e,t)=>{this._pendingAsyncCalls[r]={resolve:e,reject:t}}));try{const i=T(this,o);this._dotNetCallDispatcher.beginInvokeDotNetFromJS(r,e,t,n,i)}catch(e){this.completePendingCall(r,!1,e)}return i}receiveByteArray(e,t){this._byteArraysToBeRevived.set(e,t)}processByteArray(e){const t=this._byteArraysToBeRevived.get(e);return t?(this._byteArraysToBeRevived.delete(e),t):null}supplyDotNetStream(e,t){if(this._pendingDotNetToJSStreams.has(e)){const n=this._pendingDotNetToJSStreams.get(e);this._pendingDotNetToJSStreams.delete(e),n.resolve(t)}else{const n=new E;n.resolve(t),this._pendingDotNetToJSStreams.set(e,n)}}getDotNetStreamPromise(e){let t;if(this._pendingDotNetToJSStreams.has(e))t=this._pendingDotNetToJSStreams.get(e).streamPromise,this._pendingDotNetToJSStreams.delete(e);else{const n=new E;this._pendingDotNetToJSStreams.set(e,n),t=n.streamPromise}return t}completePendingCall(e,t,n){if(!this._pendingAsyncCalls.hasOwnProperty(e))throw new Error(`There is no pending async call with ID ${e}.`);const o=this._pendingAsyncCalls[e];delete this._pendingAsyncCalls[e],t?o.resolve(n):o.reject(n)}}function w(e){return e instanceof Error?`${e.message}\n${e.stack}`:e?e.toString():"null"}function b(e,t){const n=h[t];if(n)return n.findFunction(e);throw new Error(`JS object instance with ID ${t} does not exist (has it been disposed?).`)}function _(e){delete h[e]}e.findJSFunction=b,e.disposeJSObjectReferenceById=_;class S{constructor(e,t){this._id=e,this._callDispatcher=t}invokeMethod(e,...t){return this._callDispatcher.invokeDotNetMethod(null,e,this._id,t)}invokeMethodAsync(e,...t){return this._callDispatcher.invokeDotNetMethodAsync(null,e,this._id,t)}dispose(){this._callDispatcher.invokeDotNetMethodAsync(null,"__Dispose",this._id,null).catch((e=>console.error(e)))}serializeAsArg(){return{[o]:this._id}}}e.DotNetObject=S,p((function(e,t){if(t&&"object"==typeof t){if(t.hasOwnProperty(o))return new S(t[o],c);if(t.hasOwnProperty(n)){const e=t[n],o=h[e];if(o)return o.getWrappedObject();throw new Error(`JS object instance with Id '${e}' does not exist. It may have been disposed.`)}if(t.hasOwnProperty(r)){const e=t[r],n=c.processByteArray(e);if(void 0===n)throw new Error(`Byte array index '${e}' does not exist.`);return n}if(t.hasOwnProperty(i)){const e=t[i],n=c.getDotNetStreamPromise(e);return new C(n)}}return t}));class C{constructor(e){this._streamPromise=e}stream(){return this._streamPromise}async arrayBuffer(){return new Response(await this.stream()).arrayBuffer()}}class E{constructor(){this.streamPromise=new Promise(((e,t)=>{this.resolve=e,this.reject=t}))}}function I(e,t){switch(t){case d.Default:return e;case d.JSObjectReference:return f(e);case d.JSStreamReference:return g(e);case d.JSVoidResult:return null;default:throw new Error(`Invalid JS call result type '${t}'.`)}}let k=0;function T(e,t){k=0,c=e;const n=JSON.stringify(t,R);return c=void 0,n}function R(e,t){if(t instanceof S)return t.serializeAsArg();if(t instanceof Uint8Array){c.getDotNetCallDispatcher().sendByteArray(k,t);const e={[r]:k};return k++,e}return t}}(e||(e={})),function(e){e[e.prependFrame=1]="prependFrame",e[e.removeFrame=2]="removeFrame",e[e.setAttribute=3]="setAttribute",e[e.removeAttribute=4]="removeAttribute",e[e.updateText=5]="updateText",e[e.stepIn=6]="stepIn",e[e.stepOut=7]="stepOut",e[e.updateMarkup=8]="updateMarkup",e[e.permutationListEntry=9]="permutationListEntry",e[e.permutationListEnd=10]="permutationListEnd"}(t||(t={})),function(e){e[e.element=1]="element",e[e.text=2]="text",e[e.attribute=3]="attribute",e[e.component=4]="component",e[e.region=5]="region",e[e.elementReferenceCapture=6]="elementReferenceCapture",e[e.markup=8]="markup",e[e.namedEvent=10]="namedEvent"}(o||(o={}));class r{constructor(e,t){this.componentId=e,this.fieldValue=t}static fromEvent(e,t){const n=t.target;if(n instanceof Element){const t=function(e){return e instanceof HTMLInputElement?e.type&&"checkbox"===e.type.toLowerCase()?{value:e.checked}:{value:e.value}:e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement?{value:e.value}:null}(n);if(t)return new r(e,t.value)}return null}}const i=new Map,s=new Map,a=[];function c(e){return i.get(e)}function l(e){const t=i.get(e);return(null==t?void 0:t.browserEventName)||e}function h(e,t){e.forEach((e=>i.set(e,t)))}function d(e){const t=[];for(let n=0;ne.selected)).map((e=>e.value))}}{const e=function(e){return!!e&&"INPUT"===e.tagName&&"checkbox"===e.getAttribute("type")}(t);return{value:e?!!t.checked:t.value}}}}),h(["copy","cut","paste"],{createEventArgs:e=>({type:e.type})}),h(["drag","dragend","dragenter","dragleave","dragover","dragstart","drop"],{createEventArgs:e=>{return{...u(t=e),dataTransfer:t.dataTransfer?{dropEffect:t.dataTransfer.dropEffect,effectAllowed:t.dataTransfer.effectAllowed,files:Array.from(t.dataTransfer.files).map((e=>e.name)),items:Array.from(t.dataTransfer.items).map((e=>({kind:e.kind,type:e.type}))),types:t.dataTransfer.types}:null};var t}}),h(["focus","blur","focusin","focusout"],{createEventArgs:e=>({type:e.type})}),h(["keydown","keyup","keypress"],{createEventArgs:e=>{return{key:(t=e).key,code:t.code,location:t.location,repeat:t.repeat,ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),h(["contextmenu","click","mouseover","mouseout","mousemove","mousedown","mouseup","mouseleave","mouseenter","dblclick"],{createEventArgs:e=>u(e)}),h(["error"],{createEventArgs:e=>{return{message:(t=e).message,filename:t.filename,lineno:t.lineno,colno:t.colno,type:t.type};var t}}),h(["loadstart","timeout","abort","load","loadend","progress"],{createEventArgs:e=>{return{lengthComputable:(t=e).lengthComputable,loaded:t.loaded,total:t.total,type:t.type};var t}}),h(["touchcancel","touchend","touchmove","touchenter","touchleave","touchstart"],{createEventArgs:e=>{return{detail:(t=e).detail,touches:d(t.touches),targetTouches:d(t.targetTouches),changedTouches:d(t.changedTouches),ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),h(["gotpointercapture","lostpointercapture","pointercancel","pointerdown","pointerenter","pointerleave","pointermove","pointerout","pointerover","pointerup"],{createEventArgs:e=>{return{...u(t=e),pointerId:t.pointerId,width:t.width,height:t.height,pressure:t.pressure,tiltX:t.tiltX,tiltY:t.tiltY,pointerType:t.pointerType,isPrimary:t.isPrimary};var t}}),h(["wheel","mousewheel"],{createEventArgs:e=>{return{...u(t=e),deltaX:t.deltaX,deltaY:t.deltaY,deltaZ:t.deltaZ,deltaMode:t.deltaMode};var t}}),h(["cancel","close","toggle"],{createEventArgs:()=>({})});const p=["date","datetime-local","month","time","week"],f=new Map;let g,m,v=0;const y={async add(e,t,n){if(!n)throw new Error("initialParameters must be an object, even if empty.");const o="__bl-dynamic-root:"+(++v).toString();f.set(o,e);const r=await S().invokeMethodAsync("AddRootComponent",t,o),i=new _(r,m[t]);return await i.setParameters(n),i}};function w(e){const t=f.get(e);if(t)return f.delete(e),t}class b{invoke(e){return this._callback(e)}setCallback(t){this._selfJSObjectReference||(this._selfJSObjectReference=e.createJSObjectReference(this)),this._callback=t}getJSObjectReference(){return this._selfJSObjectReference}dispose(){this._selfJSObjectReference&&e.disposeJSObjectReference(this._selfJSObjectReference)}}class _{constructor(e,t){this._jsEventCallbackWrappers=new Map,this._componentId=e;for(const e of t)"eventcallback"===e.type&&this._jsEventCallbackWrappers.set(e.name.toLowerCase(),new b)}setParameters(e){const t={},n=Object.entries(e||{}),o=n.length;for(const[e,o]of n){const n=this._jsEventCallbackWrappers.get(e.toLowerCase());n&&o?(n.setCallback(o),t[e]=n.getJSObjectReference()):t[e]=o}return S().invokeMethodAsync("SetRootComponentParameters",this._componentId,o,t)}async dispose(){if(null!==this._componentId){await S().invokeMethodAsync("RemoveRootComponent",this._componentId),this._componentId=null;for(const e of this._jsEventCallbackWrappers.values())e.dispose()}}}function S(){if(!g)throw new Error("Dynamic root components have not been enabled in this application.");return g}const C=new Map,E=[],I=new Map;function k(t,n,o,r){var i,s;if(C.has(t))throw new Error(`Interop methods are already registered for renderer ${t}`);C.set(t,n),o&&r&&Object.keys(o).length>0&&function(t,n,o){if(g)throw new Error("Dynamic root components have already been enabled.");g=t,m=n;for(const[t,r]of Object.entries(o)){const o=e.findJSFunction(t,0);for(const e of r)o(e,n[e])}}(D(t),o,r),null===(s=null===(i=I.get(t))||void 0===i?void 0:i[0])||void 0===s||s.call(i),function(e){for(const t of E)t(e)}(t)}function T(e){return C.has(e)}function R(e,t,n){return A(e,t.eventHandlerId,(()=>D(e).invokeMethodAsync("DispatchEventAsync",t,n)))}function D(e){const t=C.get(e);if(!t)throw new Error(`No interop methods are registered for renderer ${e}`);return t}let A=(e,t,n)=>n();const x=B(["abort","blur","cancel","canplay","canplaythrough","change","close","cuechange","durationchange","emptied","ended","error","focus","load","loadeddata","loadedmetadata","loadend","loadstart","mouseenter","mouseleave","pointerenter","pointerleave","pause","play","playing","progress","ratechange","reset","scroll","seeked","seeking","stalled","submit","suspend","timeupdate","toggle","unload","volumechange","waiting","DOMNodeInsertedIntoDocument","DOMNodeRemovedFromDocument"]),N={submit:!0},P=B(["click","dblclick","mousedown","mousemove","mouseup"]);class M{constructor(e){this.browserRendererId=e,this.afterClickCallbacks=[];const t=++M.nextEventDelegatorId;this.eventsCollectionKey=`_blazorEvents_${t}`,this.eventInfoStore=new U(this.onGlobalEvent.bind(this))}setListener(e,t,n,o){const r=this.getEventHandlerInfosForElement(e,!0),i=r.getHandler(t);if(i)this.eventInfoStore.update(i.eventHandlerId,n);else{const i={element:e,eventName:t,eventHandlerId:n,renderingComponentId:o};this.eventInfoStore.add(i),r.setHandler(t,i)}}getHandler(e){return this.eventInfoStore.get(e)}removeListener(e){const t=this.eventInfoStore.remove(e);if(t){const e=t.element,n=this.getEventHandlerInfosForElement(e,!1);n&&n.removeHandler(t.eventName)}}notifyAfterClick(e){this.afterClickCallbacks.push(e),this.eventInfoStore.addGlobalListener("click")}setStopPropagation(e,t,n){this.getEventHandlerInfosForElement(e,!0).stopPropagation(t,n)}setPreventDefault(e,t,n){this.getEventHandlerInfosForElement(e,!0).preventDefault(t,n)}onGlobalEvent(e){if(!(e.target instanceof Element))return;this.dispatchGlobalEventToAllElements(e.type,e);const t=(n=e.type,s.get(n));var n;t&&t.forEach((t=>this.dispatchGlobalEventToAllElements(t,e))),"click"===e.type&&this.afterClickCallbacks.forEach((t=>t(e)))}dispatchGlobalEventToAllElements(e,t){const n=t.composedPath();let o=n.shift(),i=null,s=!1;const a=Object.prototype.hasOwnProperty.call(x,e);let l=!1;for(;o;){const u=o,p=this.getEventHandlerInfosForElement(u,!1);if(p){const n=p.getHandler(e);if(n&&(h=u,d=t.type,!((h instanceof HTMLButtonElement||h instanceof HTMLInputElement||h instanceof HTMLTextAreaElement||h instanceof HTMLSelectElement)&&Object.prototype.hasOwnProperty.call(P,d)&&h.disabled))){if(!s){const n=c(e);i=(null==n?void 0:n.createEventArgs)?n.createEventArgs(t):{},s=!0}Object.prototype.hasOwnProperty.call(N,t.type)&&t.preventDefault(),R(this.browserRendererId,{eventHandlerId:n.eventHandlerId,eventName:e,eventFieldInfo:r.fromEvent(n.renderingComponentId,t)},i)}p.stopPropagation(e)&&(l=!0),p.preventDefault(e)&&t.preventDefault()}o=a||l?void 0:n.shift()}var h,d}getEventHandlerInfosForElement(e,t){return Object.prototype.hasOwnProperty.call(e,this.eventsCollectionKey)?e[this.eventsCollectionKey]:t?e[this.eventsCollectionKey]=new L:null}}M.nextEventDelegatorId=0;class U{constructor(e){this.globalListener=e,this.infosByEventHandlerId={},this.countByEventName={},a.push(this.handleEventNameAliasAdded.bind(this))}add(e){if(this.infosByEventHandlerId[e.eventHandlerId])throw new Error(`Event ${e.eventHandlerId} is already tracked`);this.infosByEventHandlerId[e.eventHandlerId]=e,this.addGlobalListener(e.eventName)}get(e){return this.infosByEventHandlerId[e]}addGlobalListener(e){if(e=l(e),Object.prototype.hasOwnProperty.call(this.countByEventName,e))this.countByEventName[e]++;else{this.countByEventName[e]=1;const t=Object.prototype.hasOwnProperty.call(x,e);document.addEventListener(e,this.globalListener,t)}}update(e,t){if(Object.prototype.hasOwnProperty.call(this.infosByEventHandlerId,t))throw new Error(`Event ${t} is already tracked`);const n=this.infosByEventHandlerId[e];delete this.infosByEventHandlerId[e],n.eventHandlerId=t,this.infosByEventHandlerId[t]=n}remove(e){const t=this.infosByEventHandlerId[e];if(t){delete this.infosByEventHandlerId[e];const n=l(t.eventName);0==--this.countByEventName[n]&&(delete this.countByEventName[n],document.removeEventListener(n,this.globalListener))}return t}handleEventNameAliasAdded(e,t){if(Object.prototype.hasOwnProperty.call(this.countByEventName,e)){const n=this.countByEventName[e];delete this.countByEventName[e],document.removeEventListener(e,this.globalListener),this.addGlobalListener(t),this.countByEventName[t]+=n-1}}}class L{constructor(){this.handlers={},this.preventDefaultFlags=null,this.stopPropagationFlags=null}getHandler(e){return Object.prototype.hasOwnProperty.call(this.handlers,e)?this.handlers[e]:null}setHandler(e,t){this.handlers[e]=t}removeHandler(e){delete this.handlers[e]}preventDefault(e,t){return void 0!==t&&(this.preventDefaultFlags=this.preventDefaultFlags||{},this.preventDefaultFlags[e]=t),!!this.preventDefaultFlags&&this.preventDefaultFlags[e]}stopPropagation(e,t){return void 0!==t&&(this.stopPropagationFlags=this.stopPropagationFlags||{},this.stopPropagationFlags[e]=t),!!this.stopPropagationFlags&&this.stopPropagationFlags[e]}}function B(e){const t={};return e.forEach((e=>{t[e]=!0})),t}const O=Symbol(),F=Symbol(),$=Symbol();function H(e){const{start:t,end:n}=e,o=t[$];if(o){if(o!==e)throw new Error("The start component comment was already associated with another component descriptor.");return t}const r=t.parentNode;if(!r)throw new Error(`Comment not connected to the DOM ${t.textContent}`);const i=W(r,!0),s=Y(i);t[F]=i,t[$]=e;const a=W(t);if(n){const e=Y(a),o=Array.prototype.indexOf.call(s,a)+1;let r=null;for(;r!==n;){const n=s.splice(o,1)[0];if(!n)throw new Error("Could not find the end component comment in the parent logical node list");n[F]=t,e.push(n),r=n}}return a}function W(e,t){if(O in e)return e;const n=[];if(e.childNodes.length>0){if(!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");e.childNodes.forEach((t=>{const o=W(t,!0);o[F]=e,n.push(o)}))}return e[O]=n,e}function j(e){const t=Y(e);for(;t.length;)J(e,0)}function z(e,t){const n=document.createComment("!");return q(n,e,t),n}function q(e,t,n){const o=e;let r=e;if(Z(e)){const t=oe(o);if(t!==e){const n=new Range;n.setStartBefore(e),n.setEndAfter(t),r=n.extractContents()}}const i=K(o);if(i){const e=Y(i),t=Array.prototype.indexOf.call(e,o);e.splice(t,1),delete o[F]}const s=Y(t);if(n0;)J(n,0)}const o=n;o.parentNode.removeChild(o)}function K(e){return e[F]||null}function V(e,t){return Y(e)[t]}function X(e){return e[$]||null}function G(e){const t=te(e);return"http://www.w3.org/2000/svg"===t.namespaceURI&&"foreignObject"!==t.tagName}function Y(e){return e[O]}function Q(e){const t=Y(K(e));return t[Array.prototype.indexOf.call(t,e)+1]||null}function Z(e){return O in e}function ee(e,t){const n=Y(e);t.forEach((e=>{e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=oe(e.moveRangeStart)})),t.forEach((t=>{const o=document.createComment("marker");t.moveToBeforeMarker=o;const r=n[t.toSiblingIndex+1];r?r.parentNode.insertBefore(o,r):ne(o,e)})),t.forEach((e=>{const t=e.moveToBeforeMarker,n=t.parentNode,o=e.moveRangeStart,r=e.moveRangeEnd;let i=o;for(;i;){const e=i.nextSibling;if(n.insertBefore(i,t),i===r)break;i=e}n.removeChild(t)})),t.forEach((e=>{n[e.toSiblingIndex]=e.moveRangeStart}))}function te(e){if(e instanceof Element||e instanceof DocumentFragment)return e;if(e instanceof Comment)return e.parentNode;throw new Error("Not a valid logical element")}function ne(e,t){if(t instanceof Element||t instanceof DocumentFragment)t.appendChild(e);else{if(!(t instanceof Comment))throw new Error(`Cannot append node because the parent is not a valid logical element. Parent: ${t}`);{const n=Q(t);n?n.parentNode.insertBefore(e,n):ne(e,K(t))}}}function oe(e){if(e instanceof Element||e instanceof DocumentFragment)return e;const t=Q(e);if(t)return t.previousSibling;{const t=K(e);return t instanceof Element||t instanceof DocumentFragment?t.lastChild:oe(t)}}function re(e){return`_bl_${e}`}const ie="__internalId";e.attachReviver(((e,t)=>t&&"object"==typeof t&&Object.prototype.hasOwnProperty.call(t,ie)&&"string"==typeof t[ie]?function(e){const t=`[${re(e)}]`;return document.querySelector(t)}(t[ie]):t));const se="_blazorDeferredValue";function ae(e){e instanceof HTMLOptionElement?de(e):se in e&&he(e,e[se])}function ce(e){return"select-multiple"===e.type}function le(e,t){e.value=t||""}function he(e,t){e instanceof HTMLSelectElement?ce(e)?function(e,t){t||(t=[]);for(let n=0;n{Oe()&&Ne(e,(e=>{Xe(e,!0,!1)}))}))}getRootComponentCount(){return this.rootComponentIds.size}attachRootComponentToLogicalElement(e,t,n){if(we(t))throw new Error(`Root component '${e}' could not be attached because its target element is already associated with a root component`);ye(t,!0),this.attachComponentToElement(e,t),this.rootComponentIds.add(e),n||fe.add(t)}updateComponent(e,t,n,o){var r;const i=this.childComponentLocations[t];if(!i)throw new Error(`No element is currently associated with component ${t}`);fe.delete(i)&&(j(i),i instanceof Comment&&(i.textContent="!"));const s=null===(r=te(i))||void 0===r?void 0:r.getRootNode(),a=s&&s.activeElement;this.applyEdits(e,t,i,0,n,o),a instanceof HTMLElement&&s&&s.activeElement!==a&&a.focus()}disposeComponent(e){if(this.rootComponentIds.delete(e)){const t=this.childComponentLocations[e];ye(t,!1),!0===t[me]?fe.add(t):j(t)}delete this.childComponentLocations[e]}disposeEventHandler(e){this.eventDelegator.removeListener(e)}attachComponentToElement(e,t){this.childComponentLocations[e]=t}applyEdits(e,n,o,r,i,s){let a,c=0,l=r;const h=e.arrayBuilderSegmentReader,d=e.editReader,u=e.frameReader,p=h.values(i),f=h.offset(i),g=f+h.count(i);for(let i=f;i{const t=document.createElement("script");t.textContent=e.textContent,e.getAttributeNames().forEach((n=>{t.setAttribute(n,e.getAttribute(n))})),e.parentNode.replaceChild(t,e)})),ue.content));var s;let a=0;for(;i.firstChild;)q(i.firstChild,r,a++)}applyAttribute(e,t,n,o){const r=e.frameReader,i=r.attributeName(o),s=r.attributeEventHandlerId(o);if(s){const e=Se(i);return void this.eventDelegator.setListener(n,e,s,t)}const a=r.attributeValue(o);this.setOrRemoveAttributeOrProperty(n,i,a)}insertFrameRange(e,t,n,o,r,i,s){const a=o;for(let a=i;a{et(t,e)})},enableNavigationInterception:function(e){if(void 0!==Ee&&Ee!==e)throw new Error("Only one interactive runtime may enable navigation interception at a time.");Ee=e},setHasLocationChangingListeners:function(e,t){const n=je.get(e);if(!n)throw new Error(`Renderer with ID '${e}' is not listening for navigation events`);n.hasLocationChangingEventListeners=t},endLocationChanging:function(e,t){qe&&e===We&&(qe(t),qe=null)},navigateTo:function(e,t){Ve(e,t,!0)},refresh:function(e){!e&&Me()?Ue(location.href,!0):location.reload()},getBaseURI:()=>document.baseURI,getLocationHref:()=>location.href,scrollToElement:Ke};function Ke(e){const t=document.getElementById(e);return!!t&&(t.scrollIntoView(),!0)}function Ve(e,t,n=!1){const o=Le(e);!t.forceLoad&&Pe(o)?ot()?Xe(o,!1,t.replaceHistoryEntry,t.historyEntryState,n):Ue(o,t.replaceHistoryEntry):function(e,t){if(location.href===e){const t=e+"?";history.replaceState(null,"",t),location.replace(e)}else t?location.replace(e):location.href=e}(e,t.replaceHistoryEntry)}async function Xe(e,t,n,o=void 0,r=!1){if(Qe(),function(e){const t=e.indexOf("#");return t>-1&&location.href.replace(location.hash,"")===e.substring(0,t)}(e))return void function(e,t,n){Ge(e,t,n);const o=e.indexOf("#");o!==e.length-1&&Ke(e.substring(o+1))}(e,n,o);const i=nt();(r||!(null==i?void 0:i.hasLocationChangingEventListeners)||await Ze(e,o,t,i))&&(Re=!0,Ge(e,n,o),await et(t))}function Ge(e,t,n=void 0){t?history.replaceState({userState:n,_index:He},"",e):(He++,history.pushState({userState:n,_index:He},"",e))}function Ye(e){return new Promise((t=>{const n=ze;ze=()=>{ze=n,t()},history.go(e)}))}function Qe(){qe&&(qe(!1),qe=null)}function Ze(e,t,n,o){return new Promise((r=>{Qe(),We++,qe=r,o.locationChanging(We,e,t,n)}))}async function et(e,t){const n=null!=t?t:location.href;await Promise.all(Array.from(je,(async([t,o])=>{var r;T(t)&&await o.locationChanged(n,null===(r=history.state)||void 0===r?void 0:r.userState,e)})))}async function tt(e){var t,n;ze&&ot()&&await ze(e),He=null!==(n=null===(t=history.state)||void 0===t?void 0:t._index)&&void 0!==n?n:0}function nt(){const e=Fe();if(void 0!==e)return je.get(e)}function ot(){return Oe()||!Me()}const rt={focus:function(e,t){if(e instanceof HTMLElement)e.focus({preventScroll:t});else{if(!(e instanceof SVGElement))throw new Error("Unable to focus an invalid element.");if(!e.hasAttribute("tabindex"))throw new Error("Unable to focus an SVG element that does not have a tabindex.");e.focus({preventScroll:t})}},focusBySelector:function(e,t){const n=document.querySelector(e);n&&(n.hasAttribute("tabindex")||(n.tabIndex=-1),n.focus({preventScroll:!0}))}},it={init:function(e,t,n,o=50){const r=at(t);(r||document.documentElement).style.overflowAnchor="none";const i=document.createRange();u(n.parentElement)&&(t.style.display="table-row",n.style.display="table-row");const s=new IntersectionObserver((function(o){o.forEach((o=>{var r;if(!o.isIntersecting)return;i.setStartAfter(t),i.setEndBefore(n);const s=i.getBoundingClientRect().height,a=null===(r=o.rootBounds)||void 0===r?void 0:r.height;o.target===t?e.invokeMethodAsync("OnSpacerBeforeVisible",o.intersectionRect.top-o.boundingClientRect.top,s,a):o.target===n&&n.offsetHeight>0&&e.invokeMethodAsync("OnSpacerAfterVisible",o.boundingClientRect.bottom-o.intersectionRect.bottom,s,a)}))}),{root:r,rootMargin:`${o}px`});s.observe(t),s.observe(n);const a=d(t),c=d(n),{observersByDotNetObjectId:l,id:h}=ct(e);function d(e){const t={attributes:!0},n=new MutationObserver(((n,o)=>{u(e.parentElement)&&(o.disconnect(),e.style.display="table-row",o.observe(e,t)),s.unobserve(e),s.observe(e)}));return n.observe(e,t),n}function u(e){return null!==e&&(e instanceof HTMLTableElement&&""===e.style.display||"table"===e.style.display||e instanceof HTMLTableSectionElement&&""===e.style.display||"table-row-group"===e.style.display)}l[h]={intersectionObserver:s,mutationObserverBefore:a,mutationObserverAfter:c}},dispose:function(e){const{observersByDotNetObjectId:t,id:n}=ct(e),o=t[n];o&&(o.intersectionObserver.disconnect(),o.mutationObserverBefore.disconnect(),o.mutationObserverAfter.disconnect(),e.dispose(),delete t[n])}},st=Symbol();function at(e){return e&&e!==document.body&&e!==document.documentElement?"visible"!==getComputedStyle(e).overflowY?e:at(e.parentElement):null}function ct(e){var t;const n=e._callDispatcher,o=e._id;return null!==(t=n[st])&&void 0!==t||(n[st]={}),{observersByDotNetObjectId:n[st],id:o}}const lt={getAndRemoveExistingTitle:function(){var e;const t=document.head?document.head.getElementsByTagName("title"):[];if(0===t.length)return null;let n=null;for(let o=t.length-1;o>=0;o--){const r=t[o],i=r.previousSibling;i instanceof Comment&&null!==K(i)||(null===n&&(n=r.textContent),null===(e=r.parentNode)||void 0===e||e.removeChild(r))}return n}},ht={init:function(e,t){t._blazorInputFileNextFileId=0,t.addEventListener("click",(function(){t.value=""})),t.addEventListener("change",(function(){t._blazorFilesById={};const n=Array.prototype.map.call(t.files,(function(e){const n={id:++t._blazorInputFileNextFileId,lastModified:new Date(e.lastModified).toISOString(),name:e.name,size:e.size,contentType:e.type,readPromise:void 0,arrayBuffer:void 0,blob:e};return t._blazorFilesById[n.id]=n,n}));e.invokeMethodAsync("NotifyChange",n)}))},toImageFile:async function(e,t,n,o,r){const i=dt(e,t),s=await new Promise((function(e){const t=new Image;t.onload=function(){URL.revokeObjectURL(t.src),e(t)},t.onerror=function(){t.onerror=null,URL.revokeObjectURL(t.src)},t.src=URL.createObjectURL(i.blob)})),a=await new Promise((function(e){var t;const i=Math.min(1,o/s.width),a=Math.min(1,r/s.height),c=Math.min(i,a),l=document.createElement("canvas");l.width=Math.round(s.width*c),l.height=Math.round(s.height*c),null===(t=l.getContext("2d"))||void 0===t||t.drawImage(s,0,0,l.width,l.height),l.toBlob(e,n)})),c={id:++e._blazorInputFileNextFileId,lastModified:i.lastModified,name:i.name,size:(null==a?void 0:a.size)||0,contentType:n,blob:a||i.blob};return e._blazorFilesById[c.id]=c,c},readFileData:async function(e,t){return dt(e,t).blob}};function dt(e,t){const n=e._blazorFilesById[t];if(!n)throw new Error(`There is no file with ID ${t}. The file list may have changed. See https://aka.ms/aspnet/blazor-input-file-multiple-selections.`);return n}const ut=new Set,pt={enableNavigationPrompt:function(e){0===ut.size&&window.addEventListener("beforeunload",ft),ut.add(e)},disableNavigationPrompt:function(e){ut.delete(e),0===ut.size&&window.removeEventListener("beforeunload",ft)}};function ft(e){e.preventDefault(),e.returnValue=!0}async function gt(e,t,n){return e instanceof Blob?await async function(e,t,n){const o=e.slice(t,t+n),r=await o.arrayBuffer();return new Uint8Array(r)}(e,t,n):function(e,t,n){return new Uint8Array(e.buffer,e.byteOffset+t,n)}(e,t,n)}const mt=new Map,vt={navigateTo:function(e,t,n=!1){Ve(e,t instanceof Object?t:{forceLoad:t,replaceHistoryEntry:n})},registerCustomEventType:function(e,t){if(!t)throw new Error("The options parameter is required.");if(i.has(e))throw new Error(`The event '${e}' is already registered.`);if(t.browserEventName){const n=s.get(t.browserEventName);n?n.push(e):s.set(t.browserEventName,[e]),a.forEach((n=>n(e,t.browserEventName)))}i.set(e,t)},rootComponents:y,runtime:{},_internal:{navigationManager:Je,domWrapper:rt,Virtualize:it,PageTitle:lt,InputFile:ht,NavigationLock:pt,getJSDataStreamChunk:gt,attachWebRendererInterop:k}};var yt;window.Blazor=vt,function(e){e[e.Trace=0]="Trace",e[e.Debug=1]="Debug",e[e.Information=2]="Information",e[e.Warning=3]="Warning",e[e.Error=4]="Error",e[e.Critical=5]="Critical",e[e.None=6]="None"}(yt||(yt={}));class wt{log(e,t){}}wt.instance=new wt;class bt{constructor(e){this.minLevel=e}log(e,t){if(e>=this.minLevel){const n=`[${(new Date).toISOString()}] ${yt[e]}: ${t}`;switch(e){case yt.Critical:case yt.Error:console.error(n);break;case yt.Warning:console.warn(n);break;case yt.Information:console.info(n);break;default:console.log(n)}}}}function _t(e,t){switch(t){case"webassembly":return Tt(e,"webassembly");case"server":return function(e){return Tt(e,"server").sort(((e,t)=>e.sequence-t.sequence))}(e);case"auto":return Tt(e,"auto")}}const St=/^\s*Blazor-Server-Component-State:(?[a-zA-Z0-9+/=]+)$/,Ct=/^\s*Blazor-WebAssembly-Component-State:(?[a-zA-Z0-9+/=]+)$/,Et=/^\s*Blazor-Web-Initializers:(?[a-zA-Z0-9+/=]+)$/;function It(e){return kt(e,St)}function kt(e,t,n="state"){var o;if(e.nodeType===Node.COMMENT_NODE){const r=e.textContent||"",i=t.exec(r),s=i&&i.groups&&i.groups[n];return s&&(null===(o=e.parentNode)||void 0===o||o.removeChild(e)),s}if(!e.hasChildNodes())return;const r=e.childNodes;for(let e=0;e.*)$/);function Dt(e,t){const n=e.currentElement;var o,r,i;if(n&&n.nodeType===Node.COMMENT_NODE&&n.textContent){const s=Rt.exec(n.textContent),a=s&&s.groups&&s.groups.descriptor;if(!a)return;!function(e){if(e.parentNode instanceof Document)throw new Error("Root components cannot be marked as interactive. The element must be rendered statically so that scripts are not evaluated multiple times.")}(n);try{const s=function(e){const t=JSON.parse(e),{type:n}=t;if("server"!==n&&"webassembly"!==n&&"auto"!==n)throw new Error(`Invalid component type '${n}'.`);return t}(a),c=function(e,t,n){const{prerenderId:o}=e;if(o){for(;n.next()&&n.currentElement;){const e=n.currentElement;if(e.nodeType!==Node.COMMENT_NODE)continue;if(!e.textContent)continue;const t=Rt.exec(e.textContent),r=t&&t[1];if(r)return Pt(r,o),e}throw new Error(`Could not find an end component comment for '${t}'.`)}}(s,n,e);if(t!==s.type)return;switch(s.type){case"webassembly":return r=n,i=c,Nt(o=s),{...o,uniqueId:At++,start:r,end:i};case"server":return function(e,t,n){return xt(e),{...e,uniqueId:At++,start:t,end:n}}(s,n,c);case"auto":return function(e,t,n){return xt(e),Nt(e),{...e,uniqueId:At++,start:t,end:n}}(s,n,c)}}catch(e){throw new Error(`Found malformed component comment at ${n.textContent}`)}}}let At=0;function xt(e){const{descriptor:t,sequence:n}=e;if(!t)throw new Error("descriptor must be defined when using a descriptor.");if(void 0===n)throw new Error("sequence must be defined when using a descriptor.");if(!Number.isInteger(n))throw new Error(`Error parsing the sequence '${n}' for component '${JSON.stringify(e)}'`)}function Nt(e){const{assembly:t,typeName:n}=e;if(!t)throw new Error("assembly must be defined when using a descriptor.");if(!n)throw new Error("typeName must be defined when using a descriptor.");e.parameterDefinitions=e.parameterDefinitions&&atob(e.parameterDefinitions),e.parameterValues=e.parameterValues&&atob(e.parameterValues)}function Pt(e,t){const n=JSON.parse(e);if(1!==Object.keys(n).length)throw new Error(`Invalid end of component comment: '${e}'`);const o=n.prerenderId;if(!o)throw new Error(`End of component comment must have a value for the prerendered property: '${e}'`);if(o!==t)throw new Error(`End of component comment prerendered property must match the start comment prerender id: '${t}', '${o}'`)}class Mt{constructor(e){this.childNodes=e,this.currentIndex=-1,this.length=e.length}next(){return this.currentIndex++,this.currentIndex{n+=`0x${e<16?"0":""}${e.toString(16)} `})),n.substr(0,n.length-1)}(e)}'`)):"string"==typeof e&&(n=`String data of length ${e.length}`,t&&(n+=`. Content: '${e}'`)),n}function zt(e){return e&&"undefined"!=typeof ArrayBuffer&&(e instanceof ArrayBuffer||e.constructor&&"ArrayBuffer"===e.constructor.name)}async function qt(e,t,n,o,r,i){const s={},[a,c]=Vt();s[a]=c,e.log(Ot.Trace,`(${t} transport) sending data. ${jt(r,i.logMessageContent)}.`);const l=zt(r)?"arraybuffer":"text",h=await n.post(o,{content:r,headers:{...s,...i.headers},responseType:l,timeout:i.timeout,withCredentials:i.withCredentials});e.log(Ot.Trace,`(${t} transport) request complete. Response status: ${h.statusCode}.`)}class Jt{constructor(e,t){this._subject=e,this._observer=t}dispose(){const e=this._subject.observers.indexOf(this._observer);e>-1&&this._subject.observers.splice(e,1),0===this._subject.observers.length&&this._subject.cancelCallback&&this._subject.cancelCallback().catch((e=>{}))}}class Kt{constructor(e){this._minLevel=e,this.out=console}log(e,t){if(e>=this._minLevel){const n=`[${(new Date).toISOString()}] ${Ot[e]}: ${t}`;switch(e){case Ot.Critical:case Ot.Error:this.out.error(n);break;case Ot.Warning:this.out.warn(n);break;case Ot.Information:this.out.info(n);break;default:this.out.log(n)}}}}function Vt(){let e="X-SignalR-User-Agent";return Wt.isNode&&(e="User-Agent"),[e,Xt($t,Gt(),Wt.isNode?"NodeJS":"Browser",Yt())]}function Xt(e,t,n,o){let r="Microsoft SignalR/";const i=e.split(".");return r+=`${i[0]}.${i[1]}`,r+=` (${e}; `,r+=t&&""!==t?`${t}; `:"Unknown OS; ",r+=`${n}`,r+=o?`; ${o}`:"; Unknown Runtime Version",r+=")",r}function Gt(){if(!Wt.isNode)return"";switch(process.platform){case"win32":return"Windows NT";case"darwin":return"macOS";case"linux":return"Linux";default:return process.platform}}function Yt(){if(Wt.isNode)return process.versions.node}function Qt(e){return e.stack?e.stack:e.message?e.message:`${e}`}class Zt{writeHandshakeRequest(e){return Bt.write(JSON.stringify(e))}parseHandshakeResponse(e){let t,n;if(zt(e)){const o=new Uint8Array(e),r=o.indexOf(Bt.RecordSeparatorCode);if(-1===r)throw new Error("Message is incomplete.");const i=r+1;t=String.fromCharCode.apply(null,Array.prototype.slice.call(o.slice(0,i))),n=o.byteLength>i?o.slice(i).buffer:null}else{const o=e,r=o.indexOf(Bt.RecordSeparator);if(-1===r)throw new Error("Message is incomplete.");const i=r+1;t=o.substring(0,i),n=o.length>i?o.substring(i):null}const o=Bt.parse(t),r=JSON.parse(o[0]);if(r.type)throw new Error("Expected a handshake response from the server.");return[n,r]}}class en extends Error{constructor(e,t){const n=new.target.prototype;super(`${e}: Status code '${t}'`),this.statusCode=t,this.__proto__=n}}class tn extends Error{constructor(e="A timeout occurred."){const t=new.target.prototype;super(e),this.__proto__=t}}class nn extends Error{constructor(e="An abort occurred."){const t=new.target.prototype;super(e),this.__proto__=t}}class on extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="UnsupportedTransportError",this.__proto__=n}}class rn extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="DisabledTransportError",this.__proto__=n}}class sn extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="FailedToStartTransportError",this.__proto__=n}}class an extends Error{constructor(e){const t=new.target.prototype;super(e),this.errorType="FailedToNegotiateWithServerError",this.__proto__=t}}class cn extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.innerErrors=t,this.__proto__=n}}var ln,hn;!function(e){e[e.Invocation=1]="Invocation",e[e.StreamItem=2]="StreamItem",e[e.Completion=3]="Completion",e[e.StreamInvocation=4]="StreamInvocation",e[e.CancelInvocation=5]="CancelInvocation",e[e.Ping=6]="Ping",e[e.Close=7]="Close",e[e.Ack=8]="Ack",e[e.Sequence=9]="Sequence"}(ln||(ln={}));class dn{constructor(){this.observers=[]}next(e){for(const t of this.observers)t.next(e)}error(e){for(const t of this.observers)t.error&&t.error(e)}complete(){for(const e of this.observers)e.complete&&e.complete()}subscribe(e){return this.observers.push(e),new Jt(this,e)}}class un{constructor(e,t,n){this._bufferSize=1e5,this._messages=[],this._totalMessageCount=0,this._waitForSequenceMessage=!1,this._nextReceivingSequenceId=1,this._latestReceivedSequenceId=0,this._bufferedByteCount=0,this._reconnectInProgress=!1,this._protocol=e,this._connection=t,this._bufferSize=n}async _send(e){const t=this._protocol.writeMessage(e);let n=Promise.resolve();if(this._isInvocationMessage(e)){this._totalMessageCount++;let e=()=>{},o=()=>{};zt(t)?this._bufferedByteCount+=t.byteLength:this._bufferedByteCount+=t.length,this._bufferedByteCount>=this._bufferSize&&(n=new Promise(((t,n)=>{e=t,o=n}))),this._messages.push(new pn(t,this._totalMessageCount,e,o))}try{this._reconnectInProgress||await this._connection.send(t)}catch{this._disconnected()}await n}_ack(e){let t=-1;for(let n=0;nthis._nextReceivingSequenceId?this._connection.stop(new Error("Sequence ID greater than amount of messages we've received.")):this._nextReceivingSequenceId=e.sequenceId}_disconnected(){this._reconnectInProgress=!0,this._waitForSequenceMessage=!0}async _resend(){const e=0!==this._messages.length?this._messages[0]._id:this._totalMessageCount+1;await this._connection.send(this._protocol.writeMessage({type:ln.Sequence,sequenceId:e}));const t=this._messages;for(const e of t)await this._connection.send(e._message);this._reconnectInProgress=!1}_dispose(e){null!=e||(e=new Error("Unable to reconnect to server."));for(const t of this._messages)t._rejector(e)}_isInvocationMessage(e){switch(e.type){case ln.Invocation:case ln.StreamItem:case ln.Completion:case ln.StreamInvocation:case ln.CancelInvocation:return!0;case ln.Close:case ln.Sequence:case ln.Ping:case ln.Ack:return!1}}_ackTimer(){void 0===this._ackTimerHandle&&(this._ackTimerHandle=setTimeout((async()=>{try{this._reconnectInProgress||await this._connection.send(this._protocol.writeMessage({type:ln.Ack,sequenceId:this._latestReceivedSequenceId}))}catch{}clearTimeout(this._ackTimerHandle),this._ackTimerHandle=void 0}),1e3))}}class pn{constructor(e,t,n,o){this._message=e,this._id=t,this._resolver=n,this._rejector=o}}!function(e){e.Disconnected="Disconnected",e.Connecting="Connecting",e.Connected="Connected",e.Disconnecting="Disconnecting",e.Reconnecting="Reconnecting"}(hn||(hn={}));class fn{static create(e,t,n,o,r,i,s){return new fn(e,t,n,o,r,i,s)}constructor(e,t,n,o,r,i,s){this._nextKeepAlive=0,this._freezeEventListener=()=>{this._logger.log(Ot.Warning,"The page is being frozen, this will likely lead to the connection being closed and messages being lost. For more information see the docs at https://learn.microsoft.com/aspnet/core/signalr/javascript-client#bsleep")},Ht.isRequired(e,"connection"),Ht.isRequired(t,"logger"),Ht.isRequired(n,"protocol"),this.serverTimeoutInMilliseconds=null!=r?r:3e4,this.keepAliveIntervalInMilliseconds=null!=i?i:15e3,this._statefulReconnectBufferSize=null!=s?s:1e5,this._logger=t,this._protocol=n,this.connection=e,this._reconnectPolicy=o,this._handshakeProtocol=new Zt,this.connection.onreceive=e=>this._processIncomingData(e),this.connection.onclose=e=>this._connectionClosed(e),this._callbacks={},this._methods={},this._closedCallbacks=[],this._reconnectingCallbacks=[],this._reconnectedCallbacks=[],this._invocationId=0,this._receivedHandshakeResponse=!1,this._connectionState=hn.Disconnected,this._connectionStarted=!1,this._cachedPingMessage=this._protocol.writeMessage({type:ln.Ping})}get state(){return this._connectionState}get connectionId(){return this.connection&&this.connection.connectionId||null}get baseUrl(){return this.connection.baseUrl||""}set baseUrl(e){if(this._connectionState!==hn.Disconnected&&this._connectionState!==hn.Reconnecting)throw new Error("The HubConnection must be in the Disconnected or Reconnecting state to change the url.");if(!e)throw new Error("The HubConnection url must be a valid url.");this.connection.baseUrl=e}start(){return this._startPromise=this._startWithStateTransitions(),this._startPromise}async _startWithStateTransitions(){if(this._connectionState!==hn.Disconnected)return Promise.reject(new Error("Cannot start a HubConnection that is not in the 'Disconnected' state."));this._connectionState=hn.Connecting,this._logger.log(Ot.Debug,"Starting HubConnection.");try{await this._startInternal(),Wt.isBrowser&&window.document.addEventListener("freeze",this._freezeEventListener),this._connectionState=hn.Connected,this._connectionStarted=!0,this._logger.log(Ot.Debug,"HubConnection connected successfully.")}catch(e){return this._connectionState=hn.Disconnected,this._logger.log(Ot.Debug,`HubConnection failed to start successfully because of error '${e}'.`),Promise.reject(e)}}async _startInternal(){this._stopDuringStartError=void 0,this._receivedHandshakeResponse=!1;const e=new Promise(((e,t)=>{this._handshakeResolver=e,this._handshakeRejecter=t}));await this.connection.start(this._protocol.transferFormat);try{let t=this._protocol.version;this.connection.features.reconnect||(t=1);const n={protocol:this._protocol.name,version:t};if(this._logger.log(Ot.Debug,"Sending handshake request."),await this._sendMessage(this._handshakeProtocol.writeHandshakeRequest(n)),this._logger.log(Ot.Information,`Using HubProtocol '${this._protocol.name}'.`),this._cleanupTimeout(),this._resetTimeoutPeriod(),this._resetKeepAliveInterval(),await e,this._stopDuringStartError)throw this._stopDuringStartError;!!this.connection.features.reconnect&&(this._messageBuffer=new un(this._protocol,this.connection,this._statefulReconnectBufferSize),this.connection.features.disconnected=this._messageBuffer._disconnected.bind(this._messageBuffer),this.connection.features.resend=()=>{if(this._messageBuffer)return this._messageBuffer._resend()}),this.connection.features.inherentKeepAlive||await this._sendMessage(this._cachedPingMessage)}catch(e){throw this._logger.log(Ot.Debug,`Hub handshake failed with error '${e}' during start(). Stopping HubConnection.`),this._cleanupTimeout(),this._cleanupPingTimer(),await this.connection.stop(e),e}}async stop(){const e=this._startPromise;this.connection.features.reconnect=!1,this._stopPromise=this._stopInternal(),await this._stopPromise;try{await e}catch(e){}}_stopInternal(e){if(this._connectionState===hn.Disconnected)return this._logger.log(Ot.Debug,`Call to HubConnection.stop(${e}) ignored because it is already in the disconnected state.`),Promise.resolve();if(this._connectionState===hn.Disconnecting)return this._logger.log(Ot.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnecting state.`),this._stopPromise;const t=this._connectionState;return this._connectionState=hn.Disconnecting,this._logger.log(Ot.Debug,"Stopping HubConnection."),this._reconnectDelayHandle?(this._logger.log(Ot.Debug,"Connection stopped during reconnect delay. Done reconnecting."),clearTimeout(this._reconnectDelayHandle),this._reconnectDelayHandle=void 0,this._completeClose(),Promise.resolve()):(t===hn.Connected&&this._sendCloseMessage(),this._cleanupTimeout(),this._cleanupPingTimer(),this._stopDuringStartError=e||new nn("The connection was stopped before the hub handshake could complete."),this.connection.stop(e))}async _sendCloseMessage(){try{await this._sendWithProtocol(this._createCloseMessage())}catch{}}stream(e,...t){const[n,o]=this._replaceStreamingParams(t),r=this._createStreamInvocation(e,t,o);let i;const s=new dn;return s.cancelCallback=()=>{const e=this._createCancelInvocation(r.invocationId);return delete this._callbacks[r.invocationId],i.then((()=>this._sendWithProtocol(e)))},this._callbacks[r.invocationId]=(e,t)=>{t?s.error(t):e&&(e.type===ln.Completion?e.error?s.error(new Error(e.error)):s.complete():s.next(e.item))},i=this._sendWithProtocol(r).catch((e=>{s.error(e),delete this._callbacks[r.invocationId]})),this._launchStreams(n,i),s}_sendMessage(e){return this._resetKeepAliveInterval(),this.connection.send(e)}_sendWithProtocol(e){return this._messageBuffer?this._messageBuffer._send(e):this._sendMessage(this._protocol.writeMessage(e))}send(e,...t){const[n,o]=this._replaceStreamingParams(t),r=this._sendWithProtocol(this._createInvocation(e,t,!0,o));return this._launchStreams(n,r),r}invoke(e,...t){const[n,o]=this._replaceStreamingParams(t),r=this._createInvocation(e,t,!1,o);return new Promise(((e,t)=>{this._callbacks[r.invocationId]=(n,o)=>{o?t(o):n&&(n.type===ln.Completion?n.error?t(new Error(n.error)):e(n.result):t(new Error(`Unexpected message type: ${n.type}`)))};const o=this._sendWithProtocol(r).catch((e=>{t(e),delete this._callbacks[r.invocationId]}));this._launchStreams(n,o)}))}on(e,t){e&&t&&(e=e.toLowerCase(),this._methods[e]||(this._methods[e]=[]),-1===this._methods[e].indexOf(t)&&this._methods[e].push(t))}off(e,t){if(!e)return;e=e.toLowerCase();const n=this._methods[e];if(n)if(t){const o=n.indexOf(t);-1!==o&&(n.splice(o,1),0===n.length&&delete this._methods[e])}else delete this._methods[e]}onclose(e){e&&this._closedCallbacks.push(e)}onreconnecting(e){e&&this._reconnectingCallbacks.push(e)}onreconnected(e){e&&this._reconnectedCallbacks.push(e)}_processIncomingData(e){if(this._cleanupTimeout(),this._receivedHandshakeResponse||(e=this._processHandshakeResponse(e),this._receivedHandshakeResponse=!0),e){const t=this._protocol.parseMessages(e,this._logger);for(const e of t)if(!this._messageBuffer||this._messageBuffer._shouldProcessMessage(e))switch(e.type){case ln.Invocation:this._invokeClientMethod(e);break;case ln.StreamItem:case ln.Completion:{const t=this._callbacks[e.invocationId];if(t){e.type===ln.Completion&&delete this._callbacks[e.invocationId];try{t(e)}catch(e){this._logger.log(Ot.Error,`Stream callback threw error: ${Qt(e)}`)}}break}case ln.Ping:break;case ln.Close:{this._logger.log(Ot.Information,"Close message received from server.");const t=e.error?new Error("Server returned an error on close: "+e.error):void 0;!0===e.allowReconnect?this.connection.stop(t):this._stopPromise=this._stopInternal(t);break}case ln.Ack:this._messageBuffer&&this._messageBuffer._ack(e);break;case ln.Sequence:this._messageBuffer&&this._messageBuffer._resetSequence(e);break;default:this._logger.log(Ot.Warning,`Invalid message type: ${e.type}.`)}}this._resetTimeoutPeriod()}_processHandshakeResponse(e){let t,n;try{[n,t]=this._handshakeProtocol.parseHandshakeResponse(e)}catch(e){const t="Error parsing handshake response: "+e;this._logger.log(Ot.Error,t);const n=new Error(t);throw this._handshakeRejecter(n),n}if(t.error){const e="Server returned handshake error: "+t.error;this._logger.log(Ot.Error,e);const n=new Error(e);throw this._handshakeRejecter(n),n}return this._logger.log(Ot.Debug,"Server handshake complete."),this._handshakeResolver(),n}_resetKeepAliveInterval(){this.connection.features.inherentKeepAlive||(this._nextKeepAlive=(new Date).getTime()+this.keepAliveIntervalInMilliseconds,this._cleanupPingTimer())}_resetTimeoutPeriod(){if(!(this.connection.features&&this.connection.features.inherentKeepAlive||(this._timeoutHandle=setTimeout((()=>this.serverTimeout()),this.serverTimeoutInMilliseconds),void 0!==this._pingServerHandle))){let e=this._nextKeepAlive-(new Date).getTime();e<0&&(e=0),this._pingServerHandle=setTimeout((async()=>{if(this._connectionState===hn.Connected)try{await this._sendMessage(this._cachedPingMessage)}catch{this._cleanupPingTimer()}}),e)}}serverTimeout(){this.connection.stop(new Error("Server timeout elapsed without receiving a message from the server."))}async _invokeClientMethod(e){const t=e.target.toLowerCase(),n=this._methods[t];if(!n)return this._logger.log(Ot.Warning,`No client method with the name '${t}' found.`),void(e.invocationId&&(this._logger.log(Ot.Warning,`No result given for '${t}' method and invocation ID '${e.invocationId}'.`),await this._sendWithProtocol(this._createCompletionMessage(e.invocationId,"Client didn't provide a result.",null))));const o=n.slice(),r=!!e.invocationId;let i,s,a;for(const n of o)try{const o=i;i=await n.apply(this,e.arguments),r&&i&&o&&(this._logger.log(Ot.Error,`Multiple results provided for '${t}'. Sending error to server.`),a=this._createCompletionMessage(e.invocationId,"Client provided multiple results.",null)),s=void 0}catch(e){s=e,this._logger.log(Ot.Error,`A callback for the method '${t}' threw error '${e}'.`)}a?await this._sendWithProtocol(a):r?(s?a=this._createCompletionMessage(e.invocationId,`${s}`,null):void 0!==i?a=this._createCompletionMessage(e.invocationId,null,i):(this._logger.log(Ot.Warning,`No result given for '${t}' method and invocation ID '${e.invocationId}'.`),a=this._createCompletionMessage(e.invocationId,"Client didn't provide a result.",null)),await this._sendWithProtocol(a)):i&&this._logger.log(Ot.Error,`Result given for '${t}' method but server is not expecting a result.`)}_connectionClosed(e){this._logger.log(Ot.Debug,`HubConnection.connectionClosed(${e}) called while in state ${this._connectionState}.`),this._stopDuringStartError=this._stopDuringStartError||e||new nn("The underlying connection was closed before the hub handshake could complete."),this._handshakeResolver&&this._handshakeResolver(),this._cancelCallbacksWithError(e||new Error("Invocation canceled due to the underlying connection being closed.")),this._cleanupTimeout(),this._cleanupPingTimer(),this._connectionState===hn.Disconnecting?this._completeClose(e):this._connectionState===hn.Connected&&this._reconnectPolicy?this._reconnect(e):this._connectionState===hn.Connected&&this._completeClose(e)}_completeClose(e){if(this._connectionStarted){this._connectionState=hn.Disconnected,this._connectionStarted=!1,this._messageBuffer&&(this._messageBuffer._dispose(null!=e?e:new Error("Connection closed.")),this._messageBuffer=void 0),Wt.isBrowser&&window.document.removeEventListener("freeze",this._freezeEventListener);try{this._closedCallbacks.forEach((t=>t.apply(this,[e])))}catch(t){this._logger.log(Ot.Error,`An onclose callback called with error '${e}' threw error '${t}'.`)}}}async _reconnect(e){const t=Date.now();let n=0,o=void 0!==e?e:new Error("Attempting to reconnect due to a unknown error."),r=this._getNextRetryDelay(n++,0,o);if(null===r)return this._logger.log(Ot.Debug,"Connection not reconnecting because the IRetryPolicy returned null on the first reconnect attempt."),void this._completeClose(e);if(this._connectionState=hn.Reconnecting,e?this._logger.log(Ot.Information,`Connection reconnecting because of error '${e}'.`):this._logger.log(Ot.Information,"Connection reconnecting."),0!==this._reconnectingCallbacks.length){try{this._reconnectingCallbacks.forEach((t=>t.apply(this,[e])))}catch(t){this._logger.log(Ot.Error,`An onreconnecting callback called with error '${e}' threw error '${t}'.`)}if(this._connectionState!==hn.Reconnecting)return void this._logger.log(Ot.Debug,"Connection left the reconnecting state in onreconnecting callback. Done reconnecting.")}for(;null!==r;){if(this._logger.log(Ot.Information,`Reconnect attempt number ${n} will start in ${r} ms.`),await new Promise((e=>{this._reconnectDelayHandle=setTimeout(e,r)})),this._reconnectDelayHandle=void 0,this._connectionState!==hn.Reconnecting)return void this._logger.log(Ot.Debug,"Connection left the reconnecting state during reconnect delay. Done reconnecting.");try{if(await this._startInternal(),this._connectionState=hn.Connected,this._logger.log(Ot.Information,"HubConnection reconnected successfully."),0!==this._reconnectedCallbacks.length)try{this._reconnectedCallbacks.forEach((e=>e.apply(this,[this.connection.connectionId])))}catch(e){this._logger.log(Ot.Error,`An onreconnected callback called with connectionId '${this.connection.connectionId}; threw error '${e}'.`)}return}catch(e){if(this._logger.log(Ot.Information,`Reconnect attempt failed because of error '${e}'.`),this._connectionState!==hn.Reconnecting)return this._logger.log(Ot.Debug,`Connection moved to the '${this._connectionState}' from the reconnecting state during reconnect attempt. Done reconnecting.`),void(this._connectionState===hn.Disconnecting&&this._completeClose());o=e instanceof Error?e:new Error(e.toString()),r=this._getNextRetryDelay(n++,Date.now()-t,o)}}this._logger.log(Ot.Information,`Reconnect retries have been exhausted after ${Date.now()-t} ms and ${n} failed attempts. Connection disconnecting.`),this._completeClose()}_getNextRetryDelay(e,t,n){try{return this._reconnectPolicy.nextRetryDelayInMilliseconds({elapsedMilliseconds:t,previousRetryCount:e,retryReason:n})}catch(n){return this._logger.log(Ot.Error,`IRetryPolicy.nextRetryDelayInMilliseconds(${e}, ${t}) threw error '${n}'.`),null}}_cancelCallbacksWithError(e){const t=this._callbacks;this._callbacks={},Object.keys(t).forEach((n=>{const o=t[n];try{o(null,e)}catch(t){this._logger.log(Ot.Error,`Stream 'error' callback called with '${e}' threw error: ${Qt(t)}`)}}))}_cleanupPingTimer(){this._pingServerHandle&&(clearTimeout(this._pingServerHandle),this._pingServerHandle=void 0)}_cleanupTimeout(){this._timeoutHandle&&clearTimeout(this._timeoutHandle)}_createInvocation(e,t,n,o){if(n)return 0!==o.length?{arguments:t,streamIds:o,target:e,type:ln.Invocation}:{arguments:t,target:e,type:ln.Invocation};{const n=this._invocationId;return this._invocationId++,0!==o.length?{arguments:t,invocationId:n.toString(),streamIds:o,target:e,type:ln.Invocation}:{arguments:t,invocationId:n.toString(),target:e,type:ln.Invocation}}}_launchStreams(e,t){if(0!==e.length){t||(t=Promise.resolve());for(const n in e)e[n].subscribe({complete:()=>{t=t.then((()=>this._sendWithProtocol(this._createCompletionMessage(n))))},error:e=>{let o;o=e instanceof Error?e.message:e&&e.toString?e.toString():"Unknown error",t=t.then((()=>this._sendWithProtocol(this._createCompletionMessage(n,o))))},next:e=>{t=t.then((()=>this._sendWithProtocol(this._createStreamItemMessage(n,e))))}})}}_replaceStreamingParams(e){const t=[],n=[];for(let o=0;o0)&&(t=!1,this._accessToken=await this._accessTokenFactory()),this._setAuthorizationHeader(e);const n=await this._innerClient.send(e);return t&&401===n.statusCode&&this._accessTokenFactory?(this._accessToken=await this._accessTokenFactory(),this._setAuthorizationHeader(e),await this._innerClient.send(e)):n}_setAuthorizationHeader(e){e.headers||(e.headers={}),this._accessToken?e.headers[vn.Authorization]=`Bearer ${this._accessToken}`:this._accessTokenFactory&&e.headers[vn.Authorization]&&delete e.headers[vn.Authorization]}getCookieString(e){return this._innerClient.getCookieString(e)}}class _n extends wn{constructor(e){super(),this._logger=e;const t={_fetchType:void 0,_jar:void 0};var o;o=t,"undefined"==typeof fetch&&(o._jar=new(n(628).CookieJar),"undefined"==typeof fetch?o._fetchType=n(200):o._fetchType=fetch,o._fetchType=n(203)(o._fetchType,o._jar),1)?(this._fetchType=t._fetchType,this._jar=t._jar):this._fetchType=fetch.bind(function(){if("undefined"!=typeof globalThis)return globalThis;if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if(void 0!==n.g)return n.g;throw new Error("could not find global")}()),this._abortControllerType=AbortController;const r={_abortControllerType:this._abortControllerType};(function(e){return"undefined"==typeof AbortController&&(e._abortControllerType=n(778),!0)})(r)&&(this._abortControllerType=r._abortControllerType)}async send(e){if(e.abortSignal&&e.abortSignal.aborted)throw new nn;if(!e.method)throw new Error("No method defined.");if(!e.url)throw new Error("No url defined.");const t=new this._abortControllerType;let n;e.abortSignal&&(e.abortSignal.onabort=()=>{t.abort(),n=new nn});let o,r=null;if(e.timeout){const o=e.timeout;r=setTimeout((()=>{t.abort(),this._logger.log(Ot.Warning,"Timeout from HTTP request."),n=new tn}),o)}""===e.content&&(e.content=void 0),e.content&&(e.headers=e.headers||{},zt(e.content)?e.headers["Content-Type"]="application/octet-stream":e.headers["Content-Type"]="text/plain;charset=UTF-8");try{o=await this._fetchType(e.url,{body:e.content,cache:"no-cache",credentials:!0===e.withCredentials?"include":"same-origin",headers:{"X-Requested-With":"XMLHttpRequest",...e.headers},method:e.method,mode:"cors",redirect:"follow",signal:t.signal})}catch(e){if(n)throw n;throw this._logger.log(Ot.Warning,`Error from HTTP request. ${e}.`),e}finally{r&&clearTimeout(r),e.abortSignal&&(e.abortSignal.onabort=null)}if(!o.ok){const e=await Sn(o,"text");throw new en(e||o.statusText,o.status)}const i=Sn(o,e.responseType),s=await i;return new yn(o.status,o.statusText,s)}getCookieString(e){return""}}function Sn(e,t){let n;switch(t){case"arraybuffer":n=e.arrayBuffer();break;case"text":default:n=e.text();break;case"blob":case"document":case"json":throw new Error(`${t} is not supported.`)}return n}class Cn extends wn{constructor(e){super(),this._logger=e}send(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new nn):e.method?e.url?new Promise(((t,n)=>{const o=new XMLHttpRequest;o.open(e.method,e.url,!0),o.withCredentials=void 0===e.withCredentials||e.withCredentials,o.setRequestHeader("X-Requested-With","XMLHttpRequest"),""===e.content&&(e.content=void 0),e.content&&(zt(e.content)?o.setRequestHeader("Content-Type","application/octet-stream"):o.setRequestHeader("Content-Type","text/plain;charset=UTF-8"));const r=e.headers;r&&Object.keys(r).forEach((e=>{o.setRequestHeader(e,r[e])})),e.responseType&&(o.responseType=e.responseType),e.abortSignal&&(e.abortSignal.onabort=()=>{o.abort(),n(new nn)}),e.timeout&&(o.timeout=e.timeout),o.onload=()=>{e.abortSignal&&(e.abortSignal.onabort=null),o.status>=200&&o.status<300?t(new yn(o.status,o.statusText,o.response||o.responseText)):n(new en(o.response||o.responseText||o.statusText,o.status))},o.onerror=()=>{this._logger.log(Ot.Warning,`Error from HTTP request. ${o.status}: ${o.statusText}.`),n(new en(o.statusText,o.status))},o.ontimeout=()=>{this._logger.log(Ot.Warning,"Timeout from HTTP request."),n(new tn)},o.send(e.content)})):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}}class En extends wn{constructor(e){if(super(),"undefined"!=typeof fetch)this._httpClient=new _n(e);else{if("undefined"==typeof XMLHttpRequest)throw new Error("No usable HttpClient found.");this._httpClient=new Cn(e)}}send(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new nn):e.method?e.url?this._httpClient.send(e):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}getCookieString(e){return this._httpClient.getCookieString(e)}}var In,kn;!function(e){e[e.None=0]="None",e[e.WebSockets=1]="WebSockets",e[e.ServerSentEvents=2]="ServerSentEvents",e[e.LongPolling=4]="LongPolling"}(In||(In={})),function(e){e[e.Text=1]="Text",e[e.Binary=2]="Binary"}(kn||(kn={}));class Tn{constructor(){this._isAborted=!1,this.onabort=null}abort(){this._isAborted||(this._isAborted=!0,this.onabort&&this.onabort())}get signal(){return this}get aborted(){return this._isAborted}}class Rn{get pollAborted(){return this._pollAbort.aborted}constructor(e,t,n){this._httpClient=e,this._logger=t,this._pollAbort=new Tn,this._options=n,this._running=!1,this.onreceive=null,this.onclose=null}async connect(e,t){if(Ht.isRequired(e,"url"),Ht.isRequired(t,"transferFormat"),Ht.isIn(t,kn,"transferFormat"),this._url=e,this._logger.log(Ot.Trace,"(LongPolling transport) Connecting."),t===kn.Binary&&"undefined"!=typeof XMLHttpRequest&&"string"!=typeof(new XMLHttpRequest).responseType)throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");const[n,o]=Vt(),r={[n]:o,...this._options.headers},i={abortSignal:this._pollAbort.signal,headers:r,timeout:1e5,withCredentials:this._options.withCredentials};t===kn.Binary&&(i.responseType="arraybuffer");const s=`${e}&_=${Date.now()}`;this._logger.log(Ot.Trace,`(LongPolling transport) polling: ${s}.`);const a=await this._httpClient.get(s,i);200!==a.statusCode?(this._logger.log(Ot.Error,`(LongPolling transport) Unexpected response code: ${a.statusCode}.`),this._closeError=new en(a.statusText||"",a.statusCode),this._running=!1):this._running=!0,this._receiving=this._poll(this._url,i)}async _poll(e,t){try{for(;this._running;)try{const n=`${e}&_=${Date.now()}`;this._logger.log(Ot.Trace,`(LongPolling transport) polling: ${n}.`);const o=await this._httpClient.get(n,t);204===o.statusCode?(this._logger.log(Ot.Information,"(LongPolling transport) Poll terminated by server."),this._running=!1):200!==o.statusCode?(this._logger.log(Ot.Error,`(LongPolling transport) Unexpected response code: ${o.statusCode}.`),this._closeError=new en(o.statusText||"",o.statusCode),this._running=!1):o.content?(this._logger.log(Ot.Trace,`(LongPolling transport) data received. ${jt(o.content,this._options.logMessageContent)}.`),this.onreceive&&this.onreceive(o.content)):this._logger.log(Ot.Trace,"(LongPolling transport) Poll timed out, reissuing.")}catch(e){this._running?e instanceof tn?this._logger.log(Ot.Trace,"(LongPolling transport) Poll timed out, reissuing."):(this._closeError=e,this._running=!1):this._logger.log(Ot.Trace,`(LongPolling transport) Poll errored after shutdown: ${e.message}`)}}finally{this._logger.log(Ot.Trace,"(LongPolling transport) Polling complete."),this.pollAborted||this._raiseOnClose()}}async send(e){return this._running?qt(this._logger,"LongPolling",this._httpClient,this._url,e,this._options):Promise.reject(new Error("Cannot send until the transport is connected"))}async stop(){this._logger.log(Ot.Trace,"(LongPolling transport) Stopping polling."),this._running=!1,this._pollAbort.abort();try{await this._receiving,this._logger.log(Ot.Trace,`(LongPolling transport) sending DELETE request to ${this._url}.`);const e={},[t,n]=Vt();e[t]=n;const o={headers:{...e,...this._options.headers},timeout:this._options.timeout,withCredentials:this._options.withCredentials};let r;try{await this._httpClient.delete(this._url,o)}catch(e){r=e}r?r instanceof en&&(404===r.statusCode?this._logger.log(Ot.Trace,"(LongPolling transport) A 404 response was returned from sending a DELETE request."):this._logger.log(Ot.Trace,`(LongPolling transport) Error sending a DELETE request: ${r}`)):this._logger.log(Ot.Trace,"(LongPolling transport) DELETE request accepted.")}finally{this._logger.log(Ot.Trace,"(LongPolling transport) Stop finished."),this._raiseOnClose()}}_raiseOnClose(){if(this.onclose){let e="(LongPolling transport) Firing onclose event.";this._closeError&&(e+=" Error: "+this._closeError),this._logger.log(Ot.Trace,e),this.onclose(this._closeError)}}}class Dn{constructor(e,t,n,o){this._httpClient=e,this._accessToken=t,this._logger=n,this._options=o,this.onreceive=null,this.onclose=null}async connect(e,t){return Ht.isRequired(e,"url"),Ht.isRequired(t,"transferFormat"),Ht.isIn(t,kn,"transferFormat"),this._logger.log(Ot.Trace,"(SSE transport) Connecting."),this._url=e,this._accessToken&&(e+=(e.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(this._accessToken)}`),new Promise(((n,o)=>{let r,i=!1;if(t===kn.Text){if(Wt.isBrowser||Wt.isWebWorker)r=new this._options.EventSource(e,{withCredentials:this._options.withCredentials});else{const t=this._httpClient.getCookieString(e),n={};n.Cookie=t;const[o,i]=Vt();n[o]=i,r=new this._options.EventSource(e,{withCredentials:this._options.withCredentials,headers:{...n,...this._options.headers}})}try{r.onmessage=e=>{if(this.onreceive)try{this._logger.log(Ot.Trace,`(SSE transport) data received. ${jt(e.data,this._options.logMessageContent)}.`),this.onreceive(e.data)}catch(e){return void this._close(e)}},r.onerror=e=>{i?this._close():o(new Error("EventSource failed to connect. The connection could not be found on the server, either the connection ID is not present on the server, or a proxy is refusing/buffering the connection. If you have multiple servers check that sticky sessions are enabled."))},r.onopen=()=>{this._logger.log(Ot.Information,`SSE connected to ${this._url}`),this._eventSource=r,i=!0,n()}}catch(e){return void o(e)}}else o(new Error("The Server-Sent Events transport only supports the 'Text' transfer format"))}))}async send(e){return this._eventSource?qt(this._logger,"SSE",this._httpClient,this._url,e,this._options):Promise.reject(new Error("Cannot send until the transport is connected"))}stop(){return this._close(),Promise.resolve()}_close(e){this._eventSource&&(this._eventSource.close(),this._eventSource=void 0,this.onclose&&this.onclose(e))}}class An{constructor(e,t,n,o,r,i){this._logger=n,this._accessTokenFactory=t,this._logMessageContent=o,this._webSocketConstructor=r,this._httpClient=e,this.onreceive=null,this.onclose=null,this._headers=i}async connect(e,t){let n;return Ht.isRequired(e,"url"),Ht.isRequired(t,"transferFormat"),Ht.isIn(t,kn,"transferFormat"),this._logger.log(Ot.Trace,"(WebSockets transport) Connecting."),this._accessTokenFactory&&(n=await this._accessTokenFactory()),new Promise(((o,r)=>{let i;e=e.replace(/^http/,"ws");const s=this._httpClient.getCookieString(e);let a=!1;if(Wt.isReactNative){const t={},[o,r]=Vt();t[o]=r,n&&(t[vn.Authorization]=`Bearer ${n}`),s&&(t[vn.Cookie]=s),i=new this._webSocketConstructor(e,void 0,{headers:{...t,...this._headers}})}else n&&(e+=(e.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(n)}`);i||(i=new this._webSocketConstructor(e)),t===kn.Binary&&(i.binaryType="arraybuffer"),i.onopen=t=>{this._logger.log(Ot.Information,`WebSocket connected to ${e}.`),this._webSocket=i,a=!0,o()},i.onerror=e=>{let t=null;t="undefined"!=typeof ErrorEvent&&e instanceof ErrorEvent?e.error:"There was an error with the transport",this._logger.log(Ot.Information,`(WebSockets transport) ${t}.`)},i.onmessage=e=>{if(this._logger.log(Ot.Trace,`(WebSockets transport) data received. ${jt(e.data,this._logMessageContent)}.`),this.onreceive)try{this.onreceive(e.data)}catch(e){return void this._close(e)}},i.onclose=e=>{if(a)this._close(e);else{let t=null;t="undefined"!=typeof ErrorEvent&&e instanceof ErrorEvent?e.error:"WebSocket failed to connect. The connection could not be found on the server, either the endpoint may not be a SignalR endpoint, the connection ID is not present on the server, or there is a proxy blocking WebSockets. If you have multiple servers check that sticky sessions are enabled.",r(new Error(t))}}}))}send(e){return this._webSocket&&this._webSocket.readyState===this._webSocketConstructor.OPEN?(this._logger.log(Ot.Trace,`(WebSockets transport) sending data. ${jt(e,this._logMessageContent)}.`),this._webSocket.send(e),Promise.resolve()):Promise.reject("WebSocket is not in the OPEN state")}stop(){return this._webSocket&&this._close(void 0),Promise.resolve()}_close(e){this._webSocket&&(this._webSocket.onclose=()=>{},this._webSocket.onmessage=()=>{},this._webSocket.onerror=()=>{},this._webSocket.close(),this._webSocket=void 0),this._logger.log(Ot.Trace,"(WebSockets transport) socket closed."),this.onclose&&(!this._isCloseEvent(e)||!1!==e.wasClean&&1e3===e.code?e instanceof Error?this.onclose(e):this.onclose():this.onclose(new Error(`WebSocket closed with status code: ${e.code} (${e.reason||"no reason given"}).`)))}_isCloseEvent(e){return e&&"boolean"==typeof e.wasClean&&"number"==typeof e.code}}class xn{constructor(e,t={}){if(this._stopPromiseResolver=()=>{},this.features={},this._negotiateVersion=1,Ht.isRequired(e,"url"),this._logger=function(e){return void 0===e?new Kt(Ot.Information):null===e?Ft.instance:void 0!==e.log?e:new Kt(e)}(t.logger),this.baseUrl=this._resolveUrl(e),(t=t||{}).logMessageContent=void 0!==t.logMessageContent&&t.logMessageContent,"boolean"!=typeof t.withCredentials&&void 0!==t.withCredentials)throw new Error("withCredentials option was not a 'boolean' or 'undefined' value");t.withCredentials=void 0===t.withCredentials||t.withCredentials,t.timeout=void 0===t.timeout?1e5:t.timeout,"undefined"==typeof WebSocket||t.WebSocket||(t.WebSocket=WebSocket),"undefined"==typeof EventSource||t.EventSource||(t.EventSource=EventSource),this._httpClient=new bn(t.httpClient||new En(this._logger),t.accessTokenFactory),this._connectionState="Disconnected",this._connectionStarted=!1,this._options=t,this.onreceive=null,this.onclose=null}async start(e){if(e=e||kn.Binary,Ht.isIn(e,kn,"transferFormat"),this._logger.log(Ot.Debug,`Starting connection with transfer format '${kn[e]}'.`),"Disconnected"!==this._connectionState)return Promise.reject(new Error("Cannot start an HttpConnection that is not in the 'Disconnected' state."));if(this._connectionState="Connecting",this._startInternalPromise=this._startInternal(e),await this._startInternalPromise,"Disconnecting"===this._connectionState){const e="Failed to start the HttpConnection before stop() was called.";return this._logger.log(Ot.Error,e),await this._stopPromise,Promise.reject(new nn(e))}if("Connected"!==this._connectionState){const e="HttpConnection.startInternal completed gracefully but didn't enter the connection into the connected state!";return this._logger.log(Ot.Error,e),Promise.reject(new nn(e))}this._connectionStarted=!0}send(e){return"Connected"!==this._connectionState?Promise.reject(new Error("Cannot send data if the connection is not in the 'Connected' State.")):(this._sendQueue||(this._sendQueue=new Nn(this.transport)),this._sendQueue.send(e))}async stop(e){return"Disconnected"===this._connectionState?(this._logger.log(Ot.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnected state.`),Promise.resolve()):"Disconnecting"===this._connectionState?(this._logger.log(Ot.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnecting state.`),this._stopPromise):(this._connectionState="Disconnecting",this._stopPromise=new Promise((e=>{this._stopPromiseResolver=e})),await this._stopInternal(e),void await this._stopPromise)}async _stopInternal(e){this._stopError=e;try{await this._startInternalPromise}catch(e){}if(this.transport){try{await this.transport.stop()}catch(e){this._logger.log(Ot.Error,`HttpConnection.transport.stop() threw error '${e}'.`),this._stopConnection()}this.transport=void 0}else this._logger.log(Ot.Debug,"HttpConnection.transport is undefined in HttpConnection.stop() because start() failed.")}async _startInternal(e){let t=this.baseUrl;this._accessTokenFactory=this._options.accessTokenFactory,this._httpClient._accessTokenFactory=this._accessTokenFactory;try{if(this._options.skipNegotiation){if(this._options.transport!==In.WebSockets)throw new Error("Negotiation can only be skipped when using the WebSocket transport directly.");this.transport=this._constructTransport(In.WebSockets),await this._startTransport(t,e)}else{let n=null,o=0;do{if(n=await this._getNegotiationResponse(t),"Disconnecting"===this._connectionState||"Disconnected"===this._connectionState)throw new nn("The connection was stopped during negotiation.");if(n.error)throw new Error(n.error);if(n.ProtocolVersion)throw new Error("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.");if(n.url&&(t=n.url),n.accessToken){const e=n.accessToken;this._accessTokenFactory=()=>e,this._httpClient._accessToken=e,this._httpClient._accessTokenFactory=void 0}o++}while(n.url&&o<100);if(100===o&&n.url)throw new Error("Negotiate redirection limit exceeded.");await this._createTransport(t,this._options.transport,n,e)}this.transport instanceof Rn&&(this.features.inherentKeepAlive=!0),"Connecting"===this._connectionState&&(this._logger.log(Ot.Debug,"The HttpConnection connected successfully."),this._connectionState="Connected")}catch(e){return this._logger.log(Ot.Error,"Failed to start the connection: "+e),this._connectionState="Disconnected",this.transport=void 0,this._stopPromiseResolver(),Promise.reject(e)}}async _getNegotiationResponse(e){const t={},[n,o]=Vt();t[n]=o;const r=this._resolveNegotiateUrl(e);this._logger.log(Ot.Debug,`Sending negotiation request: ${r}.`);try{const e=await this._httpClient.post(r,{content:"",headers:{...t,...this._options.headers},timeout:this._options.timeout,withCredentials:this._options.withCredentials});if(200!==e.statusCode)return Promise.reject(new Error(`Unexpected status code returned from negotiate '${e.statusCode}'`));const n=JSON.parse(e.content);return(!n.negotiateVersion||n.negotiateVersion<1)&&(n.connectionToken=n.connectionId),n.useStatefulReconnect&&!0!==this._options._useStatefulReconnect?Promise.reject(new an("Client didn't negotiate Stateful Reconnect but the server did.")):n}catch(e){let t="Failed to complete negotiation with the server: "+e;return e instanceof en&&404===e.statusCode&&(t+=" Either this is not a SignalR endpoint or there is a proxy blocking the connection."),this._logger.log(Ot.Error,t),Promise.reject(new an(t))}}_createConnectUrl(e,t){return t?e+(-1===e.indexOf("?")?"?":"&")+`id=${t}`:e}async _createTransport(e,t,n,o){let r=this._createConnectUrl(e,n.connectionToken);if(this._isITransport(t))return this._logger.log(Ot.Debug,"Connection was provided an instance of ITransport, using that directly."),this.transport=t,await this._startTransport(r,o),void(this.connectionId=n.connectionId);const i=[],s=n.availableTransports||[];let a=n;for(const n of s){const s=this._resolveTransportOrError(n,t,o,!0===(null==a?void 0:a.useStatefulReconnect));if(s instanceof Error)i.push(`${n.transport} failed:`),i.push(s);else if(this._isITransport(s)){if(this.transport=s,!a){try{a=await this._getNegotiationResponse(e)}catch(e){return Promise.reject(e)}r=this._createConnectUrl(e,a.connectionToken)}try{return await this._startTransport(r,o),void(this.connectionId=a.connectionId)}catch(e){if(this._logger.log(Ot.Error,`Failed to start the transport '${n.transport}': ${e}`),a=void 0,i.push(new sn(`${n.transport} failed: ${e}`,In[n.transport])),"Connecting"!==this._connectionState){const e="Failed to select transport before stop() was called.";return this._logger.log(Ot.Debug,e),Promise.reject(new nn(e))}}}}return i.length>0?Promise.reject(new cn(`Unable to connect to the server with any of the available transports. ${i.join(" ")}`,i)):Promise.reject(new Error("None of the transports supported by the client are supported by the server."))}_constructTransport(e){switch(e){case In.WebSockets:if(!this._options.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new An(this._httpClient,this._accessTokenFactory,this._logger,this._options.logMessageContent,this._options.WebSocket,this._options.headers||{});case In.ServerSentEvents:if(!this._options.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new Dn(this._httpClient,this._httpClient._accessToken,this._logger,this._options);case In.LongPolling:return new Rn(this._httpClient,this._logger,this._options);default:throw new Error(`Unknown transport: ${e}.`)}}_startTransport(e,t){return this.transport.onreceive=this.onreceive,this.features.reconnect?this.transport.onclose=async n=>{let o=!1;if(this.features.reconnect){try{this.features.disconnected(),await this.transport.connect(e,t),await this.features.resend()}catch{o=!0}o&&this._stopConnection(n)}else this._stopConnection(n)}:this.transport.onclose=e=>this._stopConnection(e),this.transport.connect(e,t)}_resolveTransportOrError(e,t,n,o){const r=In[e.transport];if(null==r)return this._logger.log(Ot.Debug,`Skipping transport '${e.transport}' because it is not supported by this client.`),new Error(`Skipping transport '${e.transport}' because it is not supported by this client.`);if(!function(e,t){return!e||0!=(t&e)}(t,r))return this._logger.log(Ot.Debug,`Skipping transport '${In[r]}' because it was disabled by the client.`),new rn(`'${In[r]}' is disabled by the client.`,r);if(!(e.transferFormats.map((e=>kn[e])).indexOf(n)>=0))return this._logger.log(Ot.Debug,`Skipping transport '${In[r]}' because it does not support the requested transfer format '${kn[n]}'.`),new Error(`'${In[r]}' does not support ${kn[n]}.`);if(r===In.WebSockets&&!this._options.WebSocket||r===In.ServerSentEvents&&!this._options.EventSource)return this._logger.log(Ot.Debug,`Skipping transport '${In[r]}' because it is not supported in your environment.'`),new on(`'${In[r]}' is not supported in your environment.`,r);this._logger.log(Ot.Debug,`Selecting transport '${In[r]}'.`);try{return this.features.reconnect=r===In.WebSockets?o:void 0,this._constructTransport(r)}catch(e){return e}}_isITransport(e){return e&&"object"==typeof e&&"connect"in e}_stopConnection(e){if(this._logger.log(Ot.Debug,`HttpConnection.stopConnection(${e}) called while in state ${this._connectionState}.`),this.transport=void 0,e=this._stopError||e,this._stopError=void 0,"Disconnected"!==this._connectionState){if("Connecting"===this._connectionState)throw this._logger.log(Ot.Warning,`Call to HttpConnection.stopConnection(${e}) was ignored because the connection is still in the connecting state.`),new Error(`HttpConnection.stopConnection(${e}) was called while the connection is still in the connecting state.`);if("Disconnecting"===this._connectionState&&this._stopPromiseResolver(),e?this._logger.log(Ot.Error,`Connection disconnected with error '${e}'.`):this._logger.log(Ot.Information,"Connection disconnected."),this._sendQueue&&(this._sendQueue.stop().catch((e=>{this._logger.log(Ot.Error,`TransportSendQueue.stop() threw error '${e}'.`)})),this._sendQueue=void 0),this.connectionId=void 0,this._connectionState="Disconnected",this._connectionStarted){this._connectionStarted=!1;try{this.onclose&&this.onclose(e)}catch(t){this._logger.log(Ot.Error,`HttpConnection.onclose(${e}) threw error '${t}'.`)}}}else this._logger.log(Ot.Debug,`Call to HttpConnection.stopConnection(${e}) was ignored because the connection is already in the disconnected state.`)}_resolveUrl(e){if(0===e.lastIndexOf("https://",0)||0===e.lastIndexOf("http://",0))return e;if(!Wt.isBrowser)throw new Error(`Cannot resolve '${e}'.`);const t=window.document.createElement("a");return t.href=e,this._logger.log(Ot.Information,`Normalizing '${e}' to '${t.href}'.`),t.href}_resolveNegotiateUrl(e){const t=new URL(e);t.pathname.endsWith("/")?t.pathname+="negotiate":t.pathname+="/negotiate";const n=new URLSearchParams(t.searchParams);return n.has("negotiateVersion")||n.append("negotiateVersion",this._negotiateVersion.toString()),n.has("useStatefulReconnect")?"true"===n.get("useStatefulReconnect")&&(this._options._useStatefulReconnect=!0):!0===this._options._useStatefulReconnect&&n.append("useStatefulReconnect","true"),t.search=n.toString(),t.toString()}}class Nn{constructor(e){this._transport=e,this._buffer=[],this._executing=!0,this._sendBufferedData=new Pn,this._transportResult=new Pn,this._sendLoopPromise=this._sendLoop()}send(e){return this._bufferData(e),this._transportResult||(this._transportResult=new Pn),this._transportResult.promise}stop(){return this._executing=!1,this._sendBufferedData.resolve(),this._sendLoopPromise}_bufferData(e){if(this._buffer.length&&typeof this._buffer[0]!=typeof e)throw new Error(`Expected data to be of type ${typeof this._buffer} but was of type ${typeof e}`);this._buffer.push(e),this._sendBufferedData.resolve()}async _sendLoop(){for(;;){if(await this._sendBufferedData.promise,!this._executing){this._transportResult&&this._transportResult.reject("Connection stopped.");break}this._sendBufferedData=new Pn;const e=this._transportResult;this._transportResult=void 0;const t="string"==typeof this._buffer[0]?this._buffer.join(""):Nn._concatBuffers(this._buffer);this._buffer.length=0;try{await this._transport.send(t),e.resolve()}catch(t){e.reject(t)}}}static _concatBuffers(e){const t=e.map((e=>e.byteLength)).reduce(((e,t)=>e+t)),n=new Uint8Array(t);let o=0;for(const t of e)n.set(new Uint8Array(t),o),o+=t.byteLength;return n.buffer}}class Pn{constructor(){this.promise=new Promise(((e,t)=>[this._resolver,this._rejecter]=[e,t]))}resolve(){this._resolver()}reject(e){this._rejecter(e)}}class Mn{constructor(){this.name="json",this.version=2,this.transferFormat=kn.Text}parseMessages(e,t){if("string"!=typeof e)throw new Error("Invalid input for JSON hub protocol. Expected a string.");if(!e)return[];null===t&&(t=Ft.instance);const n=Bt.parse(e),o=[];for(const e of n){const n=JSON.parse(e);if("number"!=typeof n.type)throw new Error("Invalid payload.");switch(n.type){case ln.Invocation:this._isInvocationMessage(n);break;case ln.StreamItem:this._isStreamItemMessage(n);break;case ln.Completion:this._isCompletionMessage(n);break;case ln.Ping:case ln.Close:break;case ln.Ack:this._isAckMessage(n);break;case ln.Sequence:this._isSequenceMessage(n);break;default:t.log(Ot.Information,"Unknown message type '"+n.type+"' ignored.");continue}o.push(n)}return o}writeMessage(e){return Bt.write(JSON.stringify(e))}_isInvocationMessage(e){this._assertNotEmptyString(e.target,"Invalid payload for Invocation message."),void 0!==e.invocationId&&this._assertNotEmptyString(e.invocationId,"Invalid payload for Invocation message.")}_isStreamItemMessage(e){if(this._assertNotEmptyString(e.invocationId,"Invalid payload for StreamItem message."),void 0===e.item)throw new Error("Invalid payload for StreamItem message.")}_isCompletionMessage(e){if(e.result&&e.error)throw new Error("Invalid payload for Completion message.");!e.result&&e.error&&this._assertNotEmptyString(e.error,"Invalid payload for Completion message."),this._assertNotEmptyString(e.invocationId,"Invalid payload for Completion message.")}_isAckMessage(e){if("number"!=typeof e.sequenceId)throw new Error("Invalid SequenceId for Ack message.")}_isSequenceMessage(e){if("number"!=typeof e.sequenceId)throw new Error("Invalid SequenceId for Sequence message.")}_assertNotEmptyString(e,t){if("string"!=typeof e||""===e)throw new Error(t)}}const Un={trace:Ot.Trace,debug:Ot.Debug,info:Ot.Information,information:Ot.Information,warn:Ot.Warning,warning:Ot.Warning,error:Ot.Error,critical:Ot.Critical,none:Ot.None};class Ln{configureLogging(e){if(Ht.isRequired(e,"logging"),function(e){return void 0!==e.log}(e))this.logger=e;else if("string"==typeof e){const t=function(e){const t=Un[e.toLowerCase()];if(void 0!==t)return t;throw new Error(`Unknown log level: ${e}`)}(e);this.logger=new Kt(t)}else this.logger=new Kt(e);return this}withUrl(e,t){return Ht.isRequired(e,"url"),Ht.isNotEmpty(e,"url"),this.url=e,this.httpConnectionOptions="object"==typeof t?{...this.httpConnectionOptions,...t}:{...this.httpConnectionOptions,transport:t},this}withHubProtocol(e){return Ht.isRequired(e,"protocol"),this.protocol=e,this}withAutomaticReconnect(e){if(this.reconnectPolicy)throw new Error("A reconnectPolicy has already been set.");return e?Array.isArray(e)?this.reconnectPolicy=new mn(e):this.reconnectPolicy=e:this.reconnectPolicy=new mn,this}withServerTimeout(e){return Ht.isRequired(e,"milliseconds"),this._serverTimeoutInMilliseconds=e,this}withKeepAliveInterval(e){return Ht.isRequired(e,"milliseconds"),this._keepAliveIntervalInMilliseconds=e,this}withStatefulReconnect(e){return void 0===this.httpConnectionOptions&&(this.httpConnectionOptions={}),this.httpConnectionOptions._useStatefulReconnect=!0,this._statefulReconnectBufferSize=null==e?void 0:e.bufferSize,this}build(){const e=this.httpConnectionOptions||{};if(void 0===e.logger&&(e.logger=this.logger),!this.url)throw new Error("The 'HubConnectionBuilder.withUrl' method must be called before building the connection.");const t=new xn(this.url,e);return fn.create(t,this.logger||Ft.instance,this.protocol||new Mn,this.reconnectPolicy,this._serverTimeoutInMilliseconds,this._keepAliveIntervalInMilliseconds,this._statefulReconnectBufferSize)}}var Bn;!function(e){e[e.Default=0]="Default",e[e.Server=1]="Server",e[e.WebAssembly=2]="WebAssembly",e[e.WebView=3]="WebView"}(Bn||(Bn={}));var On,Fn,$n,Hn=4294967295;function Wn(e,t,n){var o=Math.floor(n/4294967296),r=n;e.setUint32(t,o),e.setUint32(t+4,r)}function jn(e,t){return 4294967296*e.getInt32(t)+e.getUint32(t+4)}var zn=("undefined"==typeof process||"never"!==(null===(On=null===process||void 0===process?void 0:process.env)||void 0===On?void 0:On.TEXT_ENCODING))&&"undefined"!=typeof TextEncoder&&"undefined"!=typeof TextDecoder;function qn(e){for(var t=e.length,n=0,o=0;o=55296&&r<=56319&&o65535&&(h-=65536,i.push(h>>>10&1023|55296),h=56320|1023&h),i.push(h)}else i.push(a);i.length>=4096&&(s+=String.fromCharCode.apply(String,i),i.length=0)}return i.length>0&&(s+=String.fromCharCode.apply(String,i)),s}var Gn,Yn=zn?new TextDecoder:null,Qn=zn?"undefined"!=typeof process&&"force"!==(null===($n=null===process||void 0===process?void 0:process.env)||void 0===$n?void 0:$n.TEXT_DECODER)?200:0:Hn,Zn=function(e,t){this.type=e,this.data=t},eo=(Gn=function(e,t){return Gn=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},Gn(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}Gn(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),to=function(e){function t(n){var o=e.call(this,n)||this,r=Object.create(t.prototype);return Object.setPrototypeOf(o,r),Object.defineProperty(o,"name",{configurable:!0,enumerable:!1,value:t.name}),o}return eo(t,e),t}(Error),no={type:-1,encode:function(e){var t,n,o,r;return e instanceof Date?function(e){var t,n=e.sec,o=e.nsec;if(n>=0&&o>=0&&n<=17179869183){if(0===o&&n<=4294967295){var r=new Uint8Array(4);return(t=new DataView(r.buffer)).setUint32(0,n),r}var i=n/4294967296,s=4294967295&n;return r=new Uint8Array(8),(t=new DataView(r.buffer)).setUint32(0,o<<2|3&i),t.setUint32(4,s),r}return r=new Uint8Array(12),(t=new DataView(r.buffer)).setUint32(0,o),Wn(t,4,n),r}((o=1e6*((t=e.getTime())-1e3*(n=Math.floor(t/1e3))),{sec:n+(r=Math.floor(o/1e9)),nsec:o-1e9*r})):null},decode:function(e){var t=function(e){var t=new DataView(e.buffer,e.byteOffset,e.byteLength);switch(e.byteLength){case 4:return{sec:t.getUint32(0),nsec:0};case 8:var n=t.getUint32(0);return{sec:4294967296*(3&n)+t.getUint32(4),nsec:n>>>2};case 12:return{sec:jn(t,4),nsec:t.getUint32(0)};default:throw new to("Unrecognized data size for timestamp (expected 4, 8, or 12): ".concat(e.length))}}(e);return new Date(1e3*t.sec+t.nsec/1e6)}},oo=function(){function e(){this.builtInEncoders=[],this.builtInDecoders=[],this.encoders=[],this.decoders=[],this.register(no)}return e.prototype.register=function(e){var t=e.type,n=e.encode,o=e.decode;if(t>=0)this.encoders[t]=n,this.decoders[t]=o;else{var r=1+t;this.builtInEncoders[r]=n,this.builtInDecoders[r]=o}},e.prototype.tryToEncode=function(e,t){for(var n=0;nthis.maxDepth)throw new Error("Too deep objects in depth ".concat(t));null==e?this.encodeNil():"boolean"==typeof e?this.encodeBoolean(e):"number"==typeof e?this.encodeNumber(e):"string"==typeof e?this.encodeString(e):this.encodeObject(e,t)},e.prototype.ensureBufferSizeToWrite=function(e){var t=this.pos+e;this.view.byteLength=0?e<128?this.writeU8(e):e<256?(this.writeU8(204),this.writeU8(e)):e<65536?(this.writeU8(205),this.writeU16(e)):e<4294967296?(this.writeU8(206),this.writeU32(e)):(this.writeU8(207),this.writeU64(e)):e>=-32?this.writeU8(224|e+32):e>=-128?(this.writeU8(208),this.writeI8(e)):e>=-32768?(this.writeU8(209),this.writeI16(e)):e>=-2147483648?(this.writeU8(210),this.writeI32(e)):(this.writeU8(211),this.writeI64(e)):this.forceFloat32?(this.writeU8(202),this.writeF32(e)):(this.writeU8(203),this.writeF64(e))},e.prototype.writeStringHeader=function(e){if(e<32)this.writeU8(160+e);else if(e<256)this.writeU8(217),this.writeU8(e);else if(e<65536)this.writeU8(218),this.writeU16(e);else{if(!(e<4294967296))throw new Error("Too long string: ".concat(e," bytes in UTF-8"));this.writeU8(219),this.writeU32(e)}},e.prototype.encodeString=function(e){if(e.length>Kn){var t=qn(e);this.ensureBufferSizeToWrite(5+t),this.writeStringHeader(t),Vn(e,this.bytes,this.pos),this.pos+=t}else t=qn(e),this.ensureBufferSizeToWrite(5+t),this.writeStringHeader(t),function(e,t,n){for(var o=e.length,r=n,i=0;i>6&31|192;else{if(s>=55296&&s<=56319&&i>12&15|224,t[r++]=s>>6&63|128):(t[r++]=s>>18&7|240,t[r++]=s>>12&63|128,t[r++]=s>>6&63|128)}t[r++]=63&s|128}else t[r++]=s}}(e,this.bytes,this.pos),this.pos+=t},e.prototype.encodeObject=function(e,t){var n=this.extensionCodec.tryToEncode(e,this.context);if(null!=n)this.encodeExtension(n);else if(Array.isArray(e))this.encodeArray(e,t);else if(ArrayBuffer.isView(e))this.encodeBinary(e);else{if("object"!=typeof e)throw new Error("Unrecognized object: ".concat(Object.prototype.toString.apply(e)));this.encodeMap(e,t)}},e.prototype.encodeBinary=function(e){var t=e.byteLength;if(t<256)this.writeU8(196),this.writeU8(t);else if(t<65536)this.writeU8(197),this.writeU16(t);else{if(!(t<4294967296))throw new Error("Too large binary: ".concat(t));this.writeU8(198),this.writeU32(t)}var n=ro(e);this.writeU8a(n)},e.prototype.encodeArray=function(e,t){var n=e.length;if(n<16)this.writeU8(144+n);else if(n<65536)this.writeU8(220),this.writeU16(n);else{if(!(n<4294967296))throw new Error("Too large array: ".concat(n));this.writeU8(221),this.writeU32(n)}for(var o=0,r=e;o0&&e<=this.maxKeyLength},e.prototype.find=function(e,t,n){e:for(var o=0,r=this.caches[n-1];o=this.maxLengthPerKey?n[Math.random()*n.length|0]=o:n.push(o)},e.prototype.decode=function(e,t,n){var o=this.find(e,t,n);if(null!=o)return this.hit++,o;this.miss++;var r=Xn(e,t,n),i=Uint8Array.prototype.slice.call(e,t,t+n);return this.store(i,r),r},e}(),co=function(e,t){var n,o,r,i,s={label:0,sent:function(){if(1&r[0])throw r[1];return r[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(i){return function(a){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,o&&(r=2&i[0]?o.return:i[0]?o.throw||((r=o.return)&&r.call(o),0):o.next)&&!(r=r.call(o,i[1])).done)return r;switch(o=0,r&&(i=[2&i[0],r.value]),i[0]){case 0:case 1:r=i;break;case 4:return s.label++,{value:i[1],done:!1};case 5:s.label++,o=i[1],i=[0];continue;case 7:i=s.ops.pop(),s.trys.pop();continue;default:if(!((r=(r=s.trys).length>0&&r[r.length-1])||6!==i[0]&&2!==i[0])){s=0;continue}if(3===i[0]&&(!r||i[1]>r[0]&&i[1]=e},e.prototype.createExtraByteError=function(e){var t=this.view,n=this.pos;return new RangeError("Extra ".concat(t.byteLength-n," of ").concat(t.byteLength," byte(s) found at buffer[").concat(e,"]"))},e.prototype.decode=function(e){this.reinitializeState(),this.setBuffer(e);var t=this.doDecodeSync();if(this.hasRemaining(1))throw this.createExtraByteError(this.pos);return t},e.prototype.decodeMulti=function(e){return co(this,(function(t){switch(t.label){case 0:this.reinitializeState(),this.setBuffer(e),t.label=1;case 1:return this.hasRemaining(1)?[4,this.doDecodeSync()]:[3,3];case 2:return t.sent(),[3,1];case 3:return[2]}}))},e.prototype.decodeAsync=function(e){var t,n,o,r,i,s,a;return i=this,void 0,a=function(){var i,s,a,c,l,h,d,u;return co(this,(function(p){switch(p.label){case 0:i=!1,p.label=1;case 1:p.trys.push([1,6,7,12]),t=lo(e),p.label=2;case 2:return[4,t.next()];case 3:if((n=p.sent()).done)return[3,5];if(a=n.value,i)throw this.createExtraByteError(this.totalPos);this.appendBuffer(a);try{s=this.doDecodeSync(),i=!0}catch(e){if(!(e instanceof fo))throw e}this.totalPos+=this.pos,p.label=4;case 4:return[3,2];case 5:return[3,12];case 6:return c=p.sent(),o={error:c},[3,12];case 7:return p.trys.push([7,,10,11]),n&&!n.done&&(r=t.return)?[4,r.call(t)]:[3,9];case 8:p.sent(),p.label=9;case 9:return[3,11];case 10:if(o)throw o.error;return[7];case 11:return[7];case 12:if(i){if(this.hasRemaining(1))throw this.createExtraByteError(this.totalPos);return[2,s]}throw h=(l=this).headByte,d=l.pos,u=l.totalPos,new RangeError("Insufficient data in parsing ".concat(so(h)," at ").concat(u," (").concat(d," in the current buffer)"))}}))},new((s=void 0)||(s=Promise))((function(e,t){function n(e){try{r(a.next(e))}catch(e){t(e)}}function o(e){try{r(a.throw(e))}catch(e){t(e)}}function r(t){var r;t.done?e(t.value):(r=t.value,r instanceof s?r:new s((function(e){e(r)}))).then(n,o)}r((a=a.apply(i,[])).next())}))},e.prototype.decodeArrayStream=function(e){return this.decodeMultiAsync(e,!0)},e.prototype.decodeStream=function(e){return this.decodeMultiAsync(e,!1)},e.prototype.decodeMultiAsync=function(e,t){return function(n,o,r){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var i,s=function(){var n,o,r,i,s,a,c,l,h;return co(this,(function(d){switch(d.label){case 0:n=t,o=-1,d.label=1;case 1:d.trys.push([1,13,14,19]),r=lo(e),d.label=2;case 2:return[4,ho(r.next())];case 3:if((i=d.sent()).done)return[3,12];if(s=i.value,t&&0===o)throw this.createExtraByteError(this.totalPos);this.appendBuffer(s),n&&(o=this.readArraySize(),n=!1,this.complete()),d.label=4;case 4:d.trys.push([4,9,,10]),d.label=5;case 5:return[4,ho(this.doDecodeSync())];case 6:return[4,d.sent()];case 7:return d.sent(),0==--o?[3,8]:[3,5];case 8:return[3,10];case 9:if(!((a=d.sent())instanceof fo))throw a;return[3,10];case 10:this.totalPos+=this.pos,d.label=11;case 11:return[3,2];case 12:return[3,19];case 13:return c=d.sent(),l={error:c},[3,19];case 14:return d.trys.push([14,,17,18]),i&&!i.done&&(h=r.return)?[4,ho(h.call(r))]:[3,16];case 15:d.sent(),d.label=16;case 16:return[3,18];case 17:if(l)throw l.error;return[7];case 18:return[7];case 19:return[2]}}))}.apply(n,o||[]),a=[];return i={},c("next"),c("throw"),c("return"),i[Symbol.asyncIterator]=function(){return this},i;function c(e){s[e]&&(i[e]=function(t){return new Promise((function(n,o){a.push([e,t,n,o])>1||l(e,t)}))})}function l(e,t){try{(n=s[e](t)).value instanceof ho?Promise.resolve(n.value.v).then(h,d):u(a[0][2],n)}catch(e){u(a[0][3],e)}var n}function h(e){l("next",e)}function d(e){l("throw",e)}function u(e,t){e(t),a.shift(),a.length&&l(a[0][0],a[0][1])}}(this,arguments)},e.prototype.doDecodeSync=function(){e:for(;;){var e=this.readHeadByte(),t=void 0;if(e>=224)t=e-256;else if(e<192)if(e<128)t=e;else if(e<144){if(0!=(o=e-128)){this.pushMapState(o),this.complete();continue e}t={}}else if(e<160){if(0!=(o=e-144)){this.pushArrayState(o),this.complete();continue e}t=[]}else{var n=e-160;t=this.decodeUtf8String(n,0)}else if(192===e)t=null;else if(194===e)t=!1;else if(195===e)t=!0;else if(202===e)t=this.readF32();else if(203===e)t=this.readF64();else if(204===e)t=this.readU8();else if(205===e)t=this.readU16();else if(206===e)t=this.readU32();else if(207===e)t=this.readU64();else if(208===e)t=this.readI8();else if(209===e)t=this.readI16();else if(210===e)t=this.readI32();else if(211===e)t=this.readI64();else if(217===e)n=this.lookU8(),t=this.decodeUtf8String(n,1);else if(218===e)n=this.lookU16(),t=this.decodeUtf8String(n,2);else if(219===e)n=this.lookU32(),t=this.decodeUtf8String(n,4);else if(220===e){if(0!==(o=this.readU16())){this.pushArrayState(o),this.complete();continue e}t=[]}else if(221===e){if(0!==(o=this.readU32())){this.pushArrayState(o),this.complete();continue e}t=[]}else if(222===e){if(0!==(o=this.readU16())){this.pushMapState(o),this.complete();continue e}t={}}else if(223===e){if(0!==(o=this.readU32())){this.pushMapState(o),this.complete();continue e}t={}}else if(196===e){var o=this.lookU8();t=this.decodeBinary(o,1)}else if(197===e)o=this.lookU16(),t=this.decodeBinary(o,2);else if(198===e)o=this.lookU32(),t=this.decodeBinary(o,4);else if(212===e)t=this.decodeExtension(1,0);else if(213===e)t=this.decodeExtension(2,0);else if(214===e)t=this.decodeExtension(4,0);else if(215===e)t=this.decodeExtension(8,0);else if(216===e)t=this.decodeExtension(16,0);else if(199===e)o=this.lookU8(),t=this.decodeExtension(o,1);else if(200===e)o=this.lookU16(),t=this.decodeExtension(o,2);else{if(201!==e)throw new to("Unrecognized type byte: ".concat(so(e)));o=this.lookU32(),t=this.decodeExtension(o,4)}this.complete();for(var r=this.stack;r.length>0;){var i=r[r.length-1];if(0===i.type){if(i.array[i.position]=t,i.position++,i.position!==i.size)continue e;r.pop(),t=i.array}else{if(1===i.type){if("string"!=(s=typeof t)&&"number"!==s)throw new to("The type of key must be string or number but "+typeof t);if("__proto__"===t)throw new to("The key __proto__ is not allowed");i.key=t,i.type=2;continue e}if(i.map[i.key]=t,i.readCount++,i.readCount!==i.size){i.key=null,i.type=1;continue e}r.pop(),t=i.map}}return t}var s},e.prototype.readHeadByte=function(){return-1===this.headByte&&(this.headByte=this.readU8()),this.headByte},e.prototype.complete=function(){this.headByte=-1},e.prototype.readArraySize=function(){var e=this.readHeadByte();switch(e){case 220:return this.readU16();case 221:return this.readU32();default:if(e<160)return e-144;throw new to("Unrecognized array type byte: ".concat(so(e)))}},e.prototype.pushMapState=function(e){if(e>this.maxMapLength)throw new to("Max length exceeded: map length (".concat(e,") > maxMapLengthLength (").concat(this.maxMapLength,")"));this.stack.push({type:1,size:e,key:null,readCount:0,map:{}})},e.prototype.pushArrayState=function(e){if(e>this.maxArrayLength)throw new to("Max length exceeded: array length (".concat(e,") > maxArrayLength (").concat(this.maxArrayLength,")"));this.stack.push({type:0,size:e,array:new Array(e),position:0})},e.prototype.decodeUtf8String=function(e,t){var n;if(e>this.maxStrLength)throw new to("Max length exceeded: UTF-8 byte length (".concat(e,") > maxStrLength (").concat(this.maxStrLength,")"));if(this.bytes.byteLengthQn?function(e,t,n){var o=e.subarray(t,t+n);return Yn.decode(o)}(this.bytes,r,e):Xn(this.bytes,r,e),this.pos+=t+e,o},e.prototype.stateIsMapKey=function(){return this.stack.length>0&&1===this.stack[this.stack.length-1].type},e.prototype.decodeBinary=function(e,t){if(e>this.maxBinLength)throw new to("Max length exceeded: bin length (".concat(e,") > maxBinLength (").concat(this.maxBinLength,")"));if(!this.hasRemaining(e+t))throw go;var n=this.pos+t,o=this.bytes.subarray(n,n+e);return this.pos+=t+e,o},e.prototype.decodeExtension=function(e,t){if(e>this.maxExtLength)throw new to("Max length exceeded: ext length (".concat(e,") > maxExtLength (").concat(this.maxExtLength,")"));var n=this.view.getInt8(this.pos+t),o=this.decodeBinary(e,t+1);return this.extensionCodec.decode(o,n,this.context)},e.prototype.lookU8=function(){return this.view.getUint8(this.pos)},e.prototype.lookU16=function(){return this.view.getUint16(this.pos)},e.prototype.lookU32=function(){return this.view.getUint32(this.pos)},e.prototype.readU8=function(){var e=this.view.getUint8(this.pos);return this.pos++,e},e.prototype.readI8=function(){var e=this.view.getInt8(this.pos);return this.pos++,e},e.prototype.readU16=function(){var e=this.view.getUint16(this.pos);return this.pos+=2,e},e.prototype.readI16=function(){var e=this.view.getInt16(this.pos);return this.pos+=2,e},e.prototype.readU32=function(){var e=this.view.getUint32(this.pos);return this.pos+=4,e},e.prototype.readI32=function(){var e=this.view.getInt32(this.pos);return this.pos+=4,e},e.prototype.readU64=function(){var e,t,n=(e=this.view,t=this.pos,4294967296*e.getUint32(t)+e.getUint32(t+4));return this.pos+=8,n},e.prototype.readI64=function(){var e=jn(this.view,this.pos);return this.pos+=8,e},e.prototype.readF32=function(){var e=this.view.getFloat32(this.pos);return this.pos+=4,e},e.prototype.readF64=function(){var e=this.view.getFloat64(this.pos);return this.pos+=8,e},e}();class yo{static write(e){let t=e.byteLength||e.length;const n=[];do{let e=127&t;t>>=7,t>0&&(e|=128),n.push(e)}while(t>0);t=e.byteLength||e.length;const o=new Uint8Array(n.length+t);return o.set(n,0),o.set(e,n.length),o.buffer}static parse(e){const t=[],n=new Uint8Array(e),o=[0,7,14,21,28];for(let r=0;r7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=r+s+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(r+s,r+s+a):n.subarray(r+s,r+s+a)),r=r+s+a}return t}}const wo=new Uint8Array([145,ln.Ping]);class bo{constructor(e){this.name="messagepack",this.version=2,this.transferFormat=kn.Binary,this._errorResult=1,this._voidResult=2,this._nonVoidResult=3,e=e||{},this._encoder=new io(e.extensionCodec,e.context,e.maxDepth,e.initialBufferSize,e.sortKeys,e.forceFloat32,e.ignoreUndefined,e.forceIntegerToFloat),this._decoder=new vo(e.extensionCodec,e.context,e.maxStrLength,e.maxBinLength,e.maxArrayLength,e.maxMapLength,e.maxExtLength)}parseMessages(e,t){if(!(n=e)||"undefined"==typeof ArrayBuffer||!(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer.");var n;null===t&&(t=Ft.instance);const o=yo.parse(e),r=[];for(const e of o){const n=this._parseMessage(e,t);n&&r.push(n)}return r}writeMessage(e){switch(e.type){case ln.Invocation:return this._writeInvocation(e);case ln.StreamInvocation:return this._writeStreamInvocation(e);case ln.StreamItem:return this._writeStreamItem(e);case ln.Completion:return this._writeCompletion(e);case ln.Ping:return yo.write(wo);case ln.CancelInvocation:return this._writeCancelInvocation(e);case ln.Close:return this._writeClose();case ln.Ack:return this._writeAck(e);case ln.Sequence:return this._writeSequence(e);default:throw new Error("Invalid message type.")}}_parseMessage(e,t){if(0===e.length)throw new Error("Invalid payload.");const n=this._decoder.decode(e);if(0===n.length||!(n instanceof Array))throw new Error("Invalid payload.");const o=n[0];switch(o){case ln.Invocation:return this._createInvocationMessage(this._readHeaders(n),n);case ln.StreamItem:return this._createStreamItemMessage(this._readHeaders(n),n);case ln.Completion:return this._createCompletionMessage(this._readHeaders(n),n);case ln.Ping:return this._createPingMessage(n);case ln.Close:return this._createCloseMessage(n);case ln.Ack:return this._createAckMessage(n);case ln.Sequence:return this._createSequenceMessage(n);default:return t.log(Ot.Information,"Unknown message type '"+o+"' ignored."),null}}_createCloseMessage(e){if(e.length<2)throw new Error("Invalid payload for Close message.");return{allowReconnect:e.length>=3?e[2]:void 0,error:e[1],type:ln.Close}}_createPingMessage(e){if(e.length<1)throw new Error("Invalid payload for Ping message.");return{type:ln.Ping}}_createInvocationMessage(e,t){if(t.length<5)throw new Error("Invalid payload for Invocation message.");const n=t[2];return n?{arguments:t[4],headers:e,invocationId:n,streamIds:[],target:t[3],type:ln.Invocation}:{arguments:t[4],headers:e,streamIds:[],target:t[3],type:ln.Invocation}}_createStreamItemMessage(e,t){if(t.length<4)throw new Error("Invalid payload for StreamItem message.");return{headers:e,invocationId:t[2],item:t[3],type:ln.StreamItem}}_createCompletionMessage(e,t){if(t.length<4)throw new Error("Invalid payload for Completion message.");const n=t[3];if(n!==this._voidResult&&t.length<5)throw new Error("Invalid payload for Completion message.");let o,r;switch(n){case this._errorResult:o=t[4];break;case this._nonVoidResult:r=t[4]}return{error:o,headers:e,invocationId:t[2],result:r,type:ln.Completion}}_createAckMessage(e){if(e.length<1)throw new Error("Invalid payload for Ack message.");return{sequenceId:e[1],type:ln.Ack}}_createSequenceMessage(e){if(e.length<1)throw new Error("Invalid payload for Sequence message.");return{sequenceId:e[1],type:ln.Sequence}}_writeInvocation(e){let t;return t=e.streamIds?this._encoder.encode([ln.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments,e.streamIds]):this._encoder.encode([ln.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments]),yo.write(t.slice())}_writeStreamInvocation(e){let t;return t=e.streamIds?this._encoder.encode([ln.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments,e.streamIds]):this._encoder.encode([ln.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments]),yo.write(t.slice())}_writeStreamItem(e){const t=this._encoder.encode([ln.StreamItem,e.headers||{},e.invocationId,e.item]);return yo.write(t.slice())}_writeCompletion(e){const t=e.error?this._errorResult:void 0!==e.result?this._nonVoidResult:this._voidResult;let n;switch(t){case this._errorResult:n=this._encoder.encode([ln.Completion,e.headers||{},e.invocationId,t,e.error]);break;case this._voidResult:n=this._encoder.encode([ln.Completion,e.headers||{},e.invocationId,t]);break;case this._nonVoidResult:n=this._encoder.encode([ln.Completion,e.headers||{},e.invocationId,t,e.result])}return yo.write(n.slice())}_writeCancelInvocation(e){const t=this._encoder.encode([ln.CancelInvocation,e.headers||{},e.invocationId]);return yo.write(t.slice())}_writeClose(){const e=this._encoder.encode([ln.Close,null]);return yo.write(e.slice())}_writeAck(e){const t=this._encoder.encode([ln.Ack,e.sequenceId]);return yo.write(t.slice())}_writeSequence(e){const t=this._encoder.encode([ln.Sequence,e.sequenceId]);return yo.write(t.slice())}_readHeaders(e){const t=e[1];if("object"!=typeof t)throw new Error("Invalid headers.");return t}}const _o="function"==typeof TextDecoder?new TextDecoder("utf-8"):null,So=_o?_o.decode.bind(_o):function(e){let t=0;const n=e.length,o=[],r=[];for(;t65535&&(r-=65536,o.push(r>>>10&1023|55296),r=56320|1023&r),o.push(r)}o.length>1024&&(r.push(String.fromCharCode.apply(null,o)),o.length=0)}return r.push(String.fromCharCode.apply(null,o)),r.join("")},Co=Math.pow(2,32),Eo=Math.pow(2,21)-1;function Io(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24}function ko(e,t){return e[t]+(e[t+1]<<8)+(e[t+2]<<16)+(e[t+3]<<24>>>0)}function To(e,t){const n=ko(e,t+4);if(n>Eo)throw new Error(`Cannot read uint64 with high order part ${n}, because the result would exceed Number.MAX_SAFE_INTEGER.`);return n*Co+ko(e,t)}class Ro{constructor(e){this.batchData=e;const t=new No(e);this.arrayRangeReader=new Po(e),this.arrayBuilderSegmentReader=new Mo(e),this.diffReader=new Do(e),this.editReader=new Ao(e,t),this.frameReader=new xo(e,t)}updatedComponents(){return Io(this.batchData,this.batchData.length-20)}referenceFrames(){return Io(this.batchData,this.batchData.length-16)}disposedComponentIds(){return Io(this.batchData,this.batchData.length-12)}disposedEventHandlerIds(){return Io(this.batchData,this.batchData.length-8)}updatedComponentsEntry(e,t){const n=e+4*t;return Io(this.batchData,n)}referenceFramesEntry(e,t){return e+20*t}disposedComponentIdsEntry(e,t){const n=e+4*t;return Io(this.batchData,n)}disposedEventHandlerIdsEntry(e,t){const n=e+8*t;return To(this.batchData,n)}}class Do{constructor(e){this.batchDataUint8=e}componentId(e){return Io(this.batchDataUint8,e)}edits(e){return e+4}editsEntry(e,t){return e+16*t}}class Ao{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}editType(e){return Io(this.batchDataUint8,e)}siblingIndex(e){return Io(this.batchDataUint8,e+4)}newTreeIndex(e){return Io(this.batchDataUint8,e+8)}moveToSiblingIndex(e){return Io(this.batchDataUint8,e+8)}removedAttributeName(e){const t=Io(this.batchDataUint8,e+12);return this.stringReader.readString(t)}}class xo{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}frameType(e){return Io(this.batchDataUint8,e)}subtreeLength(e){return Io(this.batchDataUint8,e+4)}elementReferenceCaptureId(e){const t=Io(this.batchDataUint8,e+4);return this.stringReader.readString(t)}componentId(e){return Io(this.batchDataUint8,e+8)}elementName(e){const t=Io(this.batchDataUint8,e+8);return this.stringReader.readString(t)}textContent(e){const t=Io(this.batchDataUint8,e+4);return this.stringReader.readString(t)}markupContent(e){const t=Io(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeName(e){const t=Io(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeValue(e){const t=Io(this.batchDataUint8,e+8);return this.stringReader.readString(t)}attributeEventHandlerId(e){return To(this.batchDataUint8,e+12)}}class No{constructor(e){this.batchDataUint8=e,this.stringTableStartIndex=Io(e,e.length-4)}readString(e){if(-1===e)return null;{const n=Io(this.batchDataUint8,this.stringTableStartIndex+4*e),o=function(e,t){let n=0,o=0;for(let r=0;r<4;r++){const i=e[t+r];if(n|=(127&i)<this.nextBatchId)return this.fatalError?(this.logger.log(yt.Debug,`Received a new batch ${e} but errored out on a previous batch ${this.nextBatchId-1}`),void await n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())):void this.logger.log(yt.Debug,`Waiting for batch ${this.nextBatchId}. Batch ${e} not processed.`);try{this.nextBatchId++,this.logger.log(yt.Debug,`Applying batch ${e}.`),xe(Bn.Server,new Ro(t)),await this.completeBatch(n,e)}catch(t){throw this.fatalError=t.toString(),this.logger.log(yt.Error,`There was an error applying batch ${e}.`),n.send("OnRenderCompleted",e,t.toString()),t}}getLastBatchid(){return this.nextBatchId-1}async completeBatch(e,t){try{await e.send("OnRenderCompleted",t,null)}catch{this.logger.log(yt.Warning,`Failed to deliver completion notification for render '${t}'.`)}}}let Lo=!1;function Bo(){const e=document.querySelector("#blazor-error-ui");e&&(e.style.display="block"),Lo||(Lo=!0,document.querySelectorAll("#blazor-error-ui .reload").forEach((e=>{e.onclick=function(e){location.reload(),e.preventDefault()}})),document.querySelectorAll("#blazor-error-ui .dismiss").forEach((e=>{e.onclick=function(e){const t=document.querySelector("#blazor-error-ui");t&&(t.style.display="none"),e.preventDefault()}})))}class Oo{constructor(t,n,o,r){this._firstUpdate=!0,this._renderingFailed=!1,this._disposed=!1,this._circuitId=void 0,this._applicationState=n,this._componentManager=t,this._options=o,this._logger=r,this._renderQueue=new Uo(this._logger),this._dispatcher=e.attachDispatcher(this)}start(){if(this.isDisposedOrDisposing())throw new Error("Cannot start a disposed circuit.");return this._startPromise||(this._startPromise=this.startCore()),this._startPromise}updateRootComponents(e){var t,n;return this._firstUpdate?(this._firstUpdate=!1,null===(t=this._connection)||void 0===t?void 0:t.send("UpdateRootComponents",e,this._applicationState)):null===(n=this._connection)||void 0===n?void 0:n.send("UpdateRootComponents",e,"")}async startCore(){if(this._connection=await this.startConnection(),this._connection.state!==hn.Connected)return!1;const e=JSON.stringify(this._componentManager.initialComponents.map((e=>Ut(e))));if(this._circuitId=await this._connection.invoke("StartCircuit",Je.getBaseURI(),Je.getLocationHref(),e,this._applicationState||""),!this._circuitId)return!1;for(const e of this._options.circuitHandlers)e.onCircuitOpened&&e.onCircuitOpened();return!0}async startConnection(){var e,t;const n=new bo;n.name="blazorpack";const o=(new Ln).withUrl("_blazor").withHubProtocol(n);this._options.configureSignalR(o);const r=o.build();r.on("JS.AttachComponent",((e,t)=>De(Bn.Server,this.resolveElement(t),e,!1))),r.on("JS.BeginInvokeJS",this._dispatcher.beginInvokeJSFromDotNet.bind(this._dispatcher)),r.on("JS.EndInvokeDotNet",this._dispatcher.endInvokeDotNetFromJS.bind(this._dispatcher)),r.on("JS.ReceiveByteArray",this._dispatcher.receiveByteArray.bind(this._dispatcher)),r.on("JS.BeginTransmitStream",(e=>{const t=new ReadableStream({start:t=>{r.stream("SendDotNetStreamToJS",e).subscribe({next:e=>t.enqueue(e),complete:()=>t.close(),error:e=>t.error(e)})}});this._dispatcher.supplyDotNetStream(e,t)})),r.on("JS.RenderBatch",(async(e,t)=>{var n,o;this._logger.log(Ot.Debug,`Received render batch with id ${e} and ${t.byteLength} bytes.`),await this._renderQueue.processBatch(e,t,this._connection),null===(o=(n=this._componentManager).onAfterRenderBatch)||void 0===o||o.call(n,Bn.Server)})),r.on("JS.EndUpdateRootComponents",(e=>{var t,n;null===(n=(t=this._componentManager).onAfterUpdateRootComponents)||void 0===n||n.call(t,e)})),r.on("JS.EndLocationChanging",vt._internal.navigationManager.endLocationChanging),r.onclose((e=>{this._interopMethodsForReconnection=function(e){const t=C.get(e);if(!t)throw new Error(`Interop methods are not registered for renderer ${e}`);return C.delete(e),t}(Bn.Server),this._disposed||this._renderingFailed||this._options.reconnectionHandler.onConnectionDown(this._options.reconnectionOptions,e)})),r.on("JS.Error",(e=>{this._renderingFailed=!0,this.unhandledError(e),Bo()}));try{await r.start()}catch(e){if(this.unhandledError(e),"FailedToNegotiateWithServerError"===e.errorType)throw e;Bo(),e.innerErrors&&(e.innerErrors.some((e=>"UnsupportedTransportError"===e.errorType&&e.transport===In.WebSockets))?this._logger.log(Ot.Error,"Unable to connect, please ensure you are using an updated browser that supports WebSockets."):e.innerErrors.some((e=>"FailedToStartTransportError"===e.errorType&&e.transport===In.WebSockets))?this._logger.log(Ot.Error,"Unable to connect, please ensure WebSockets are available. A VPN or proxy may be blocking the connection."):e.innerErrors.some((e=>"DisabledTransportError"===e.errorType&&e.transport===In.LongPolling))&&this._logger.log(Ot.Error,"Unable to initiate a SignalR connection to the server. This might be because the server is not configured to support WebSockets. For additional details, visit https://aka.ms/blazor-server-websockets-error."))}return(null===(t=null===(e=r.connection)||void 0===e?void 0:e.features)||void 0===t?void 0:t.inherentKeepAlive)&&this._logger.log(Ot.Warning,"Failed to connect via WebSockets, using the Long Polling fallback transport. This may be due to a VPN or proxy blocking the connection. To troubleshoot this, visit https://aka.ms/blazor-server-using-fallback-long-polling."),r}async disconnect(){var e;await(null===(e=this._connection)||void 0===e?void 0:e.stop())}async reconnect(){if(!this._circuitId)throw new Error("Circuit host not initialized.");return this._connection.state===hn.Connected||(this._connection=await this.startConnection(),this._interopMethodsForReconnection&&(k(Bn.Server,this._interopMethodsForReconnection),this._interopMethodsForReconnection=void 0),!!await this._connection.invoke("ConnectCircuit",this._circuitId)&&(this._options.reconnectionHandler.onConnectionUp(),!0))}beginInvokeDotNetFromJS(e,t,n,o,r){this.throwIfDispatchingWhenDisposed(),this._connection.send("BeginInvokeDotNetFromJS",e?e.toString():null,t,n,o||0,r)}endInvokeJSFromDotNet(e,t,n){this.throwIfDispatchingWhenDisposed(),this._connection.send("EndInvokeJSFromDotNet",e,t,n)}sendByteArray(e,t){this.throwIfDispatchingWhenDisposed(),this._connection.send("ReceiveByteArray",e,t)}throwIfDispatchingWhenDisposed(){if(this._disposed)throw new Error("The circuit associated with this dispatcher is no longer available.")}sendLocationChanged(e,t,n){return this._connection.send("OnLocationChanged",e,t,n)}sendLocationChanging(e,t,n,o){return this._connection.send("OnLocationChanging",e,t,n,o)}sendJsDataStream(e,t,n){return function(e,t,n,o){setTimeout((async()=>{let r=5,i=(new Date).valueOf();try{const s=t instanceof Blob?t.size:t.byteLength;let a=0,c=0;for(;a1)await e.send("ReceiveJSDataChunk",n,c,h,null);else{if(!await e.invoke("ReceiveJSDataChunk",n,c,h,null))break;const t=(new Date).valueOf(),o=t-i;i=t,r=Math.max(1,Math.round(500/Math.max(1,o)))}a+=l,c++}}catch(t){await e.send("ReceiveJSDataChunk",n,-1,null,t.toString())}}),0)}(this._connection,e,t,n)}resolveElement(e){const t=w(e);if(t)return W(t,!0);const n=Number.parseInt(e);if(!Number.isNaN(n))return H(this._componentManager.resolveRootComponent(n));throw new Error(`Invalid sequence number or identifier '${e}'.`)}getRootComponentManager(){return this._componentManager}unhandledError(e){this._logger.log(Ot.Error,e),this.disconnect()}getDisconnectFormData(){const e=new FormData,t=this._circuitId;return e.append("circuitId",t),e}didRenderingFail(){return this._renderingFailed}isDisposedOrDisposing(){return void 0!==this._disposePromise}sendDisconnectBeacon(){if(this._disposed)return;const e=this.getDisconnectFormData();this._disposed=navigator.sendBeacon("_blazor/disconnect",e)}dispose(){return this._disposePromise||(this._disposePromise=this.disposeCore()),this._disposePromise}async disposeCore(){var e;if(!this._startPromise)return void(this._disposed=!0);await this._startPromise,this._disposed=!0,null===(e=this._connection)||void 0===e||e.stop();const t=this.getDisconnectFormData();fetch("_blazor/disconnect",{method:"POST",body:t});for(const e of this._options.circuitHandlers)e.onCircuitClosed&&e.onCircuitClosed()}}function Fo(e){const t={...$o,...e};return e&&e.reconnectionOptions&&(t.reconnectionOptions={...$o.reconnectionOptions,...e.reconnectionOptions}),t}const $o={configureSignalR:e=>{},logLevel:yt.Warning,initializers:void 0,circuitHandlers:[],reconnectionOptions:{maxRetries:8,retryIntervalMilliseconds:2e4,dialogId:"components-reconnect-modal"}};class Ho{constructor(e,t,n,o){this.maxRetries=t,this.document=n,this.logger=o,this.modal=this.document.createElement("div"),this.modal.id=e,this.maxRetries=t,this.modal.style.cssText=["position: fixed","top: 0","right: 0","bottom: 0","left: 0","z-index: 1050","display: none","overflow: hidden","background-color: #fff","opacity: 0.8","text-align: center","font-weight: bold","transition: visibility 0s linear 500ms"].join(";"),this.message=this.document.createElement("h5"),this.message.style.cssText="margin-top: 20px",this.button=this.document.createElement("button"),this.button.style.cssText="margin:5px auto 5px",this.button.textContent="Retry";const r=this.document.createElement("a");r.addEventListener("click",(()=>location.reload())),r.textContent="reload",this.reloadParagraph=this.document.createElement("p"),this.reloadParagraph.textContent="Alternatively, ",this.reloadParagraph.appendChild(r),this.modal.appendChild(this.message),this.modal.appendChild(this.button),this.modal.appendChild(this.reloadParagraph),this.loader=this.getLoader(),this.message.after(this.loader),this.button.addEventListener("click",(async()=>{this.show();try{await vt.reconnect()||this.rejected()}catch(e){this.logger.log(yt.Error,e),this.failed()}}))}show(){this.document.contains(this.modal)||this.document.body.appendChild(this.modal),this.modal.style.display="block",this.loader.style.display="inline-block",this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.textContent="Attempting to reconnect to the server...",this.modal.style.visibility="hidden",setTimeout((()=>{this.modal.style.visibility="visible"}),0)}update(e){this.message.textContent=`Attempting to reconnect to the server: ${e} of ${this.maxRetries}`}hide(){this.modal.style.display="none"}failed(){this.button.style.display="block",this.reloadParagraph.style.display="none",this.loader.style.display="none";const e=this.document.createTextNode("Reconnection failed. Try "),t=this.document.createElement("a");t.textContent="reloading",t.setAttribute("href",""),t.addEventListener("click",(()=>location.reload()));const n=this.document.createTextNode(" the page if you're unable to reconnect.");this.message.replaceChildren(e,t,n)}rejected(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.loader.style.display="none";const e=this.document.createTextNode("Could not reconnect to the server. "),t=this.document.createElement("a");t.textContent="Reload",t.setAttribute("href",""),t.addEventListener("click",(()=>location.reload()));const n=this.document.createTextNode(" the page to restore functionality.");this.message.replaceChildren(e,t,n)}getLoader(){const e=this.document.createElement("div");return e.style.cssText=["border: 0.3em solid #f3f3f3","border-top: 0.3em solid #3498db","border-radius: 50%","width: 2em","height: 2em","display: inline-block"].join(";"),e.animate([{transform:"rotate(0deg)"},{transform:"rotate(360deg)"}],{duration:2e3,iterations:1/0}),e}}class Wo{constructor(e,t,n){this.dialog=e,this.maxRetries=t,this.document=n,this.document=n;const o=this.document.getElementById(Wo.MaxRetriesId);o&&(o.innerText=this.maxRetries.toString())}show(){this.removeClasses(),this.dialog.classList.add(Wo.ShowClassName)}update(e){const t=this.document.getElementById(Wo.CurrentAttemptId);t&&(t.innerText=e.toString())}hide(){this.removeClasses(),this.dialog.classList.add(Wo.HideClassName)}failed(){this.removeClasses(),this.dialog.classList.add(Wo.FailedClassName)}rejected(){this.removeClasses(),this.dialog.classList.add(Wo.RejectedClassName)}removeClasses(){this.dialog.classList.remove(Wo.ShowClassName,Wo.HideClassName,Wo.FailedClassName,Wo.RejectedClassName)}}Wo.ShowClassName="components-reconnect-show",Wo.HideClassName="components-reconnect-hide",Wo.FailedClassName="components-reconnect-failed",Wo.RejectedClassName="components-reconnect-rejected",Wo.MaxRetriesId="components-reconnect-max-retries",Wo.CurrentAttemptId="components-reconnect-current-attempt";class jo{constructor(e,t,n){this._currentReconnectionProcess=null,this._logger=e,this._reconnectionDisplay=t,this._reconnectCallback=n||vt.reconnect}onConnectionDown(e,t){if(!this._reconnectionDisplay){const t=document.getElementById(e.dialogId);this._reconnectionDisplay=t?new Wo(t,e.maxRetries,document):new Ho(e.dialogId,e.maxRetries,document,this._logger)}this._currentReconnectionProcess||(this._currentReconnectionProcess=new zo(e,this._logger,this._reconnectCallback,this._reconnectionDisplay))}onConnectionUp(){this._currentReconnectionProcess&&(this._currentReconnectionProcess.dispose(),this._currentReconnectionProcess=null)}}class zo{constructor(e,t,n,o){this.logger=t,this.reconnectCallback=n,this.isDisposed=!1,this.reconnectDisplay=o,this.reconnectDisplay.show(),this.attemptPeriodicReconnection(e)}dispose(){this.isDisposed=!0,this.reconnectDisplay.hide()}async attemptPeriodicReconnection(e){for(let t=0;tzo.MaximumFirstRetryInterval?zo.MaximumFirstRetryInterval:e.retryIntervalMilliseconds;if(await this.delay(n),this.isDisposed)break;try{return await this.reconnectCallback()?void 0:void this.reconnectDisplay.rejected()}catch(e){this.logger.log(yt.Error,e)}}this.reconnectDisplay.failed()}delay(e){return new Promise((t=>setTimeout(t,e)))}}zo.MaximumFirstRetryInterval=3e3;class qo{constructor(e=!0,t,n,o=0){this.singleRuntime=e,this.logger=t,this.webRendererId=o,this.afterStartedCallbacks=[],n&&this.afterStartedCallbacks.push(...n)}async importInitializersAsync(e,t){await Promise.all(e.map((e=>async function(e,n){const o=function(e){const t=document.baseURI;return t.endsWith("/")?`${t}${e}`:`${t}/${e}`}(n),r=await import(o);if(void 0!==r){if(e.singleRuntime){const{beforeStart:n,afterStarted:o,beforeWebAssemblyStart:s,afterWebAssemblyStarted:a,beforeServerStart:c,afterServerStarted:l}=r;let h=n;e.webRendererId===Bn.Server&&c&&(h=c),e.webRendererId===Bn.WebAssembly&&s&&(h=s);let d=o;return e.webRendererId===Bn.Server&&l&&(d=l),e.webRendererId===Bn.WebAssembly&&a&&(d=a),i(e,h,d,t)}return function(e,t,n){var r;const s=n[0],{beforeStart:a,afterStarted:c,beforeWebStart:l,afterWebStarted:h,beforeWebAssemblyStart:d,afterWebAssemblyStarted:u,beforeServerStart:p,afterServerStarted:f}=t,g=!(l||h||d||u||p||f||!a&&!c),m=g&&s.enableClassicInitializers;if(g&&!s.enableClassicInitializers)null===(r=e.logger)||void 0===r||r.log(yt.Warning,`Initializer '${o}' will be ignored because multiple runtimes are available. use 'before(web|webAssembly|server)Start' and 'after(web|webAssembly|server)Started?' instead.)`);else if(m)return i(e,a,c,n);if(function(e){e.webAssembly?e.webAssembly.initializers||(e.webAssembly.initializers={beforeStart:[],afterStarted:[]}):e.webAssembly={initializers:{beforeStart:[],afterStarted:[]}},e.circuit?e.circuit.initializers||(e.circuit.initializers={beforeStart:[],afterStarted:[]}):e.circuit={initializers:{beforeStart:[],afterStarted:[]}}}(s),d&&s.webAssembly.initializers.beforeStart.push(d),u&&s.webAssembly.initializers.afterStarted.push(u),p&&s.circuit.initializers.beforeStart.push(p),f&&s.circuit.initializers.afterStarted.push(f),h&&e.afterStartedCallbacks.push(h),l)return l(s)}(e,r,t)}function i(e,t,n,o){if(n&&e.afterStartedCallbacks.push(n),t)return t(...o)}}(this,e))))}async invokeAfterStartedCallbacks(e){const t=function(e){var t;return null===(t=I.get(e))||void 0===t?void 0:t[1]}(this.webRendererId);t&&await t,await Promise.all(this.afterStartedCallbacks.map((t=>t(e))))}}let Jo,Ko,Vo,Xo,Go,Yo,Qo,Zo;function er(e){if(Xo)throw new Error("Circuit options have already been configured.");if(Xo)throw new Error("WebAssembly options have already been configured.");Jo=async function(e){const t=await e;Xo=Fo(t)}(e)}function tr(e){if(void 0!==Yo)throw new Error("Blazor Server has already started.");return Yo=new Promise(nr.bind(null,e)),Yo}async function nr(e,t,n){await Jo;const o=await async function(e){if(e.initializers)return await Promise.all(e.initializers.beforeStart.map((t=>t(e)))),new qo(!1,void 0,e.initializers.afterStarted,Bn.Server);const t=await fetch("_blazor/initializers",{method:"GET",credentials:"include",cache:"no-cache"}),n=await t.json(),o=new qo(!0,void 0,void 0,Bn.Server);return await o.importInitializersAsync(n,[e]),o}(Xo);if(Ko=It(document)||"",Go=new bt(Xo.logLevel),Vo=new Oo(e,Ko,Xo,Go),Go.log(yt.Information,"Starting up Blazor server-side application."),vt.reconnect=async()=>!(Vo.didRenderingFail()||!await Vo.reconnect()&&(Go.log(yt.Information,"Reconnection attempt to the circuit was rejected by the server. This may indicate that the associated state is no longer available on the server."),1)),vt.defaultReconnectionHandler=new jo(Go),Xo.reconnectionHandler=Xo.reconnectionHandler||vt.defaultReconnectionHandler,vt._internal.navigationManager.listenForNavigationEvents(Bn.Server,((e,t,n)=>Vo.sendLocationChanged(e,t,n)),((e,t,n,o)=>Vo.sendLocationChanging(e,t,n,o))),vt._internal.forceCloseConnection=()=>Vo.disconnect(),vt._internal.sendJSDataStream=(e,t,n)=>Vo.sendJsDataStream(e,t,n),!await Vo.start())return Go.log(yt.Error,"Failed to start the circuit."),void t();const r=()=>{Vo.sendDisconnectBeacon()};vt.disconnect=r,window.addEventListener("unload",r,{capture:!1,once:!0}),Go.log(yt.Information,"Blazor server-side application started."),o.invokeAfterStartedCallbacks(vt),t()}async function or(){if(!Yo)throw new Error("Cannot start the circuit until Blazor Server has started.");return!(!Vo||Vo.isDisposedOrDisposing())||(Qo?await Qo:(await Yo,(!Vo||!Vo.didRenderingFail())&&(Vo&&Vo.isDisposedOrDisposing()&&(Ko=It(document)||"",Vo=new Oo(Vo.getRootComponentManager(),Ko,Xo,Go)),Qo=Vo.start(),async function(e){await e,Qo===e&&(Qo=void 0)}(Qo),Qo)))}function rr(e){if(Vo&&!Vo.isDisposedOrDisposing())return Vo.updateRootComponents(e);!async function(e){await Yo,await or()&&Vo.updateRootComponents(e)}(e)}function ir(e){return Zo=e,Zo}var sr,ar;const cr=navigator,lr=cr.userAgentData&&cr.userAgentData.brands,hr=lr&&lr.length>0?lr.some((e=>"Google Chrome"===e.brand||"Microsoft Edge"===e.brand||"Chromium"===e.brand)):window.chrome,dr=null!==(ar=null===(sr=cr.userAgentData)||void 0===sr?void 0:sr.platform)&&void 0!==ar?ar:navigator.platform;function ur(e){return 0!==e.debugLevel&&(hr||navigator.userAgent.includes("Firefox"))}let pr,fr,gr,mr,vr,yr,wr;const br=Math.pow(2,32),_r=Math.pow(2,21)-1;let Sr=null;function Cr(e){return fr.getI32(e)}const Er={load:function(e,t){return async function(e,t){const{dotnet:n}=await async function(e){if("undefined"==typeof WebAssembly||!WebAssembly.validate)throw new Error("This browser does not support WebAssembly.");let t="_framework/dotnet.js";if(e.loadBootResource){const n="dotnetjs",o=e.loadBootResource(n,"dotnet.js",t,"","js-module-dotnet");if("string"==typeof o)t=o;else if(o)throw new Error(`For a ${n} resource, custom loaders must supply a URI string.`)}const n=new URL(t,document.baseURI).toString();return await import(n)}(e),o=function(e,t){const n={maxParallelDownloads:1e6,enableDownloadRetry:!1,applicationEnvironment:e.environment},o={...window.Module||{},onConfigLoaded:async n=>{n.environmentVariables||(n.environmentVariables={}),"sharded"===n.globalizationMode&&(n.environmentVariables.__BLAZOR_SHARDED_ICU="1"),vt._internal.getApplicationEnvironment=()=>n.applicationEnvironment,null==t||t(n),wr=await async function(e,t){var n,o,r;if(e.initializers)return await Promise.all(e.initializers.beforeStart.map((t=>t(e)))),new qo(!1,void 0,e.initializers.afterStarted,Bn.WebAssembly);{const i=[e,null!==(o=null===(n=t.resources)||void 0===n?void 0:n.extensions)&&void 0!==o?o:{}],s=new qo(!0,void 0,void 0,Bn.WebAssembly),a=Object.keys((null===(r=null==t?void 0:t.resources)||void 0===r?void 0:r.libraryInitializers)||{});return await s.importInitializersAsync(a,i),s}}(e,n)},onDownloadResourceProgress:Ir,config:n,disableDotnet6Compatibility:!1,out:Tr,err:Rr};return o}(e,t);e.applicationCulture&&n.withApplicationCulture(e.applicationCulture),e.environment&&n.withApplicationEnvironment(e.environment),e.loadBootResource&&n.withResourceLoader(e.loadBootResource),n.withModuleConfig(o),e.configureRuntime&&e.configureRuntime(n),yr=await n.create()}(e,t)},start:function(){return async function(){if(!yr)throw new Error("The runtime must be loaded it gets configured.");const{MONO:t,BINDING:n,Module:o,setModuleImports:r,INTERNAL:i,getConfig:s,invokeLibraryInitializers:a}=yr;gr=o,pr=n,fr=t,vr=i,function(e){const t=dr.match(/^Mac/i)?"Cmd":"Alt";ur(e)&&console.info(`Debugging hotkey: Shift+${t}+D (when application has focus)`),document.addEventListener("keydown",(t=>{t.shiftKey&&(t.metaKey||t.altKey)&&"KeyD"===t.code&&(ur(e)?navigator.userAgent.includes("Firefox")?async function(){const e=await fetch(`_framework/debug?url=${encodeURIComponent(location.href)}&isFirefox=true`);200!==e.status&&console.warn(await e.text())}():hr?function(){const e=document.createElement("a");e.href=`_framework/debug?url=${encodeURIComponent(location.href)}`,e.target="_blank",e.rel="noopener noreferrer",e.click()}():console.error("Currently, only Microsoft Edge (80+), Google Chrome, or Chromium, are supported for debugging."):console.error("Cannot start debugging, because the application was not compiled with debugging enabled."))}))}(s()),vt.runtime=yr,vt._internal.dotNetCriticalError=Rr,r("blazor-internal",{Blazor:{_internal:vt._internal}});const c=await yr.getAssemblyExports("Microsoft.AspNetCore.Components.WebAssembly");return Object.assign(vt._internal,{dotNetExports:{...c.Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime}}),mr=e.attachDispatcher({beginInvokeDotNetFromJS:(e,t,n,o,r)=>{if(Ar(),!o&&!t)throw new Error("Either assemblyName or dotNetObjectId must have a non null value.");const i=o?o.toString():t;vt._internal.dotNetExports.BeginInvokeDotNet(e?e.toString():null,i,n,r)},endInvokeJSFromDotNet:(e,t,n)=>{vt._internal.dotNetExports.EndInvokeJS(n)},sendByteArray:(e,t)=>{vt._internal.dotNetExports.ReceiveByteArrayFromJS(e,t)},invokeDotNetFromJS:(e,t,n,o)=>(Ar(),vt._internal.dotNetExports.InvokeDotNet(e||null,t,null!=n?n:0,o))}),{invokeLibraryInitializers:a}}()},callEntryPoint:async function(){try{await yr.runMain(yr.getConfig().mainAssemblyName,[])}catch(e){console.error(e),Bo()}},toUint8Array:function(e){const t=Dr(e),n=Cr(t),o=new Uint8Array(n);return o.set(gr.HEAPU8.subarray(t+4,t+4+n)),o},getArrayLength:function(e){return Cr(Dr(e))},getArrayEntryPtr:function(e,t,n){return Dr(e)+4+t*n},getObjectFieldsBaseAddress:function(e){return e+8},readInt16Field:function(e,t){return n=e+(t||0),fr.getI16(n);var n},readInt32Field:function(e,t){return Cr(e+(t||0))},readUint64Field:function(e,t){return function(e){const t=e>>2,n=gr.HEAPU32[t+1];if(n>_r)throw new Error(`Cannot read uint64 with high order part ${n}, because the result would exceed Number.MAX_SAFE_INTEGER.`);return n*br+gr.HEAPU32[t]}(e+(t||0))},readFloatField:function(e,t){return n=e+(t||0),fr.getF32(n);var n},readObjectField:function(e,t){return Cr(e+(t||0))},readStringField:function(e,t,n){const o=Cr(e+(t||0));if(0===o)return null;if(n){const e=pr.unbox_mono_obj(o);return"boolean"==typeof e?e?"":null:e}return pr.conv_string(o)},readStructField:function(e,t){return e+(t||0)},beginHeapLock:function(){return Ar(),Sr=xr.create(),Sr},invokeWhenHeapUnlocked:function(e){Sr?Sr.enqueuePostReleaseAction(e):e()}};function Ir(e,t){const n=e/t*100;document.documentElement.style.setProperty("--blazor-load-percentage",`${n}%`),document.documentElement.style.setProperty("--blazor-load-percentage-text",`"${Math.floor(n)}%"`)}const kr=["DEBUGGING ENABLED"],Tr=e=>kr.indexOf(e)<0&&console.log(e),Rr=e=>{console.error(e||"(null)"),Bo()};function Dr(e){return e+12}function Ar(){if(Sr)throw new Error("Assertion failed - heap is currently locked")}class xr{enqueuePostReleaseAction(e){this.postReleaseActions||(this.postReleaseActions=[]),this.postReleaseActions.push(e)}release(){var e;if(Sr!==this)throw new Error("Trying to release a lock which isn't current");for(vr.mono_wasm_gc_unlock(),Sr=null;null===(e=this.postReleaseActions)||void 0===e?void 0:e.length;)this.postReleaseActions.shift()(),Ar()}static create(){return vr.mono_wasm_gc_lock(),new xr}}class Nr{constructor(e){this.batchAddress=e,this.arrayRangeReader=Pr,this.arrayBuilderSegmentReader=Mr,this.diffReader=Ur,this.editReader=Lr,this.frameReader=Br}updatedComponents(){return Zo.readStructField(this.batchAddress,0)}referenceFrames(){return Zo.readStructField(this.batchAddress,Pr.structLength)}disposedComponentIds(){return Zo.readStructField(this.batchAddress,2*Pr.structLength)}disposedEventHandlerIds(){return Zo.readStructField(this.batchAddress,3*Pr.structLength)}updatedComponentsEntry(e,t){return Or(e,t,Ur.structLength)}referenceFramesEntry(e,t){return Or(e,t,Br.structLength)}disposedComponentIdsEntry(e,t){const n=Or(e,t,4);return Zo.readInt32Field(n)}disposedEventHandlerIdsEntry(e,t){const n=Or(e,t,8);return Zo.readUint64Field(n)}}const Pr={structLength:8,values:e=>Zo.readObjectField(e,0),count:e=>Zo.readInt32Field(e,4)},Mr={structLength:12,values:e=>{const t=Zo.readObjectField(e,0),n=Zo.getObjectFieldsBaseAddress(t);return Zo.readObjectField(n,0)},offset:e=>Zo.readInt32Field(e,4),count:e=>Zo.readInt32Field(e,8)},Ur={structLength:4+Mr.structLength,componentId:e=>Zo.readInt32Field(e,0),edits:e=>Zo.readStructField(e,4),editsEntry:(e,t)=>Or(e,t,Lr.structLength)},Lr={structLength:20,editType:e=>Zo.readInt32Field(e,0),siblingIndex:e=>Zo.readInt32Field(e,4),newTreeIndex:e=>Zo.readInt32Field(e,8),moveToSiblingIndex:e=>Zo.readInt32Field(e,8),removedAttributeName:e=>Zo.readStringField(e,16)},Br={structLength:36,frameType:e=>Zo.readInt16Field(e,4),subtreeLength:e=>Zo.readInt32Field(e,8),elementReferenceCaptureId:e=>Zo.readStringField(e,16),componentId:e=>Zo.readInt32Field(e,12),elementName:e=>Zo.readStringField(e,16),textContent:e=>Zo.readStringField(e,16),markupContent:e=>Zo.readStringField(e,16),attributeName:e=>Zo.readStringField(e,16),attributeValue:e=>Zo.readStringField(e,24,!0),attributeEventHandlerId:e=>Zo.readUint64Field(e,8)};function Or(e,t,n){return Zo.getArrayEntryPtr(e,t,n)}class Fr{constructor(e){this.componentManager=e}resolveRegisteredElement(e){const t=Number.parseInt(e);if(!Number.isNaN(t))return H(this.componentManager.resolveRootComponent(t))}getParameterValues(e){return this.componentManager.initialComponents[e].parameterValues}getParameterDefinitions(e){return this.componentManager.initialComponents[e].parameterDefinitions}getTypeName(e){return this.componentManager.initialComponents[e].typeName}getAssembly(e){return this.componentManager.initialComponents[e].assembly}getCount(){return this.componentManager.initialComponents.length}}let $r,Hr,Wr,jr,zr,qr=!1,Jr=!1,Kr=!0,Vr=!1;const Xr=new Promise((e=>{zr=e}));let Gr;const Yr=new Promise((e=>{Gr=e}));let Qr;function Zr(e){if(void 0!==jr)throw new Error("Blazor WebAssembly has already started.");return jr=new Promise(ei.bind(null,e)),jr}async function ei(e,t,n){(function(){if(window.parent!==window&&!window.opener&&window.frameElement){const e=window.sessionStorage&&window.sessionStorage["Microsoft.AspNetCore.Components.WebAssembly.Authentication.CachedAuthSettings"],t=e&&JSON.parse(e);return t&&t.redirect_uri&&location.href.startsWith(t.redirect_uri)}return!1})()&&await new Promise((()=>{}));const o=ti();!function(e){const t=A;A=(e,n,o)=>{((e,t,n)=>{const o=Ae(e);(null==o?void 0:o.eventDelegator.getHandler(t))&&Er.invokeWhenHeapUnlocked(n)})(e,n,(()=>t(e,n,o)))}}(),vt._internal.applyHotReload=(e,t,n,o)=>{mr.invokeDotNetStaticMethod("Microsoft.AspNetCore.Components.WebAssembly","ApplyHotReloadDelta",e,t,n,o)},vt._internal.getApplyUpdateCapabilities=()=>mr.invokeDotNetStaticMethod("Microsoft.AspNetCore.Components.WebAssembly","GetApplyUpdateCapabilities"),vt._internal.invokeJSFromDotNet=oi,vt._internal.invokeJSJson=ri,vt._internal.endInvokeDotNetFromJS=ii,vt._internal.receiveWebAssemblyDotNetDataStream=si,vt._internal.receiveByteArray=ai;const r=ir(Er);vt.platform=r,vt._internal.renderBatch=(e,t)=>{const n=Er.beginHeapLock();try{xe(e,new Nr(t))}finally{n.release()}},vt._internal.navigationManager.listenForNavigationEvents(Bn.WebAssembly,(async(e,t,n)=>{await mr.invokeDotNetStaticMethodAsync("Microsoft.AspNetCore.Components.WebAssembly","NotifyLocationChanged",e,t,n)}),(async(e,t,n,o)=>{const r=await mr.invokeDotNetStaticMethodAsync("Microsoft.AspNetCore.Components.WebAssembly","NotifyLocationChangingAsync",t,n,o);vt._internal.navigationManager.endLocationChanging(e,r)}));const i=new Fr(e);let s;vt._internal.registeredComponents={getRegisteredComponentsCount:()=>i.getCount(),getAssembly:e=>i.getAssembly(e),getTypeName:e=>i.getTypeName(e),getParameterDefinitions:e=>i.getParameterDefinitions(e)||"",getParameterValues:e=>i.getParameterValues(e)||""},vt._internal.getPersistedState=()=>kt(document,Ct)||"",vt._internal.getInitialComponentsUpdate=()=>Yr,vt._internal.updateRootComponents=e=>{var t;return null===(t=vt._internal.dotNetExports)||void 0===t?void 0:t.UpdateRootComponentsCore(e)},vt._internal.endUpdateRootComponents=t=>{var n;return null===(n=e.onAfterUpdateRootComponents)||void 0===n?void 0:n.call(e,t)},vt._internal.attachRootComponentToElement=(e,t,n)=>{const o=i.resolveRegisteredElement(e);o?De(n,o,t,!1):function(e,t,n){const o="::before";let r=!1;if(e.endsWith("::after"))e=e.slice(0,-7),r=!0;else if(e.endsWith(o))throw new Error(`The '${o}' selector is not supported.`);const i=w(e)||document.querySelector(e);if(!i)throw new Error(`Could not find any element matching selector '${e}'.`);De(n,W(i,!0),t,r)}(e,t,n)};try{await o,s=await r.start()}catch(e){throw new Error(`Failed to start platform. Reason: ${e}`)}r.callEntryPoint(),wr.invokeAfterStartedCallbacks(vt),Jr=!0,t()}function ti(){return null!=Wr||(Wr=(async()=>{await Hr;const e=null!=$r?$r:{},t=null==$r?void 0:$r.configureRuntime;e.configureRuntime=e=>{null==t||t(e),Vr&&e.withEnvironmentVariable("__BLAZOR_WEBASSEMBLY_WAIT_FOR_ROOT_COMPONENTS","true")},await Er.load(e,zr),qr=!0})()),Wr}function ni(){return qr}function oi(t,n,o,r){const i=Er.readStringField(t,0),s=Er.readInt32Field(t,4),a=Er.readStringField(t,8),c=Er.readUint64Field(t,20);if(null!==a){const e=Er.readUint64Field(t,12);if(0!==e)return mr.beginInvokeJSFromDotNet(e,i,a,s,c),0;{const e=mr.invokeJSFromDotNet(i,a,s,c);return null===e?0:pr.js_string_to_mono_string(e)}}{const t=e.findJSFunction(i,c).call(null,n,o,r);switch(s){case e.JSCallResultType.Default:return t;case e.JSCallResultType.JSObjectReference:return e.createJSObjectReference(t).__jsObjectId;case e.JSCallResultType.JSStreamReference:{const n=e.createJSStreamReference(t),o=JSON.stringify(n);return pr.js_string_to_mono_string(o)}case e.JSCallResultType.JSVoidResult:return null;default:throw new Error(`Invalid JS call result type '${s}'.`)}}}function ri(e,t,n,o,r){return 0!==r?(mr.beginInvokeJSFromDotNet(r,e,o,n,t),null):mr.invokeJSFromDotNet(e,o,n,t)}function ii(e,t,n){mr.endInvokeDotNetFromJS(e,t,n)}function si(e,t,n,o){!function(e,t,n,o,r){let i=mt.get(t);if(!i){const n=new ReadableStream({start(e){mt.set(t,e),i=e}});e.supplyDotNetStream(t,n)}r?(i.error(r),mt.delete(t)):0===o?(i.close(),mt.delete(t)):i.enqueue(n.length===o?n:n.subarray(0,o))}(mr,e,t,n,o)}function ai(e,t){mr.receiveByteArray(e,t)}function ci(e,t){t.namespaceURI?e.setAttributeNS(t.namespaceURI,t.name,t.value):e.setAttribute(t.name,t.value)}Hr=new Promise((e=>{Qr=e}));const li="data-permanent";var hi,di;!function(e){e[e.None=0]="None",e[e.Some=1]="Some",e[e.Infinite=2]="Infinite"}(hi||(hi={})),function(e){e.Keep="keep",e.Update="update",e.Insert="insert",e.Delete="delete"}(di||(di={}));class ui{static create(e,t,n){return 0===t&&n===e.length?e:new ui(e,t,n)}constructor(e,t,n){this.source=e,this.startIndex=t,this.length=n}item(e){return this.source.item(e+this.startIndex)}forEach(e,t){for(let t=0;t=n&&s>=o&&r(e.item(i),t.item(s))===hi.None;)i--,s--,a++;return a}(e,t,o,o,n),i=function(e){var t;const n=[];let o=e.length-1,r=(null===(t=e[o])||void 0===t?void 0:t.length)-1;for(;o>0||r>0;){const t=0===o?di.Insert:0===r?di.Delete:e[o][r];switch(n.unshift(t),t){case di.Keep:case di.Update:o--,r--;break;case di.Insert:r--;break;case di.Delete:o--}}return n}(function(e,t,n){const o=[],r=[],i=e.length,s=t.length;if(0===i&&0===s)return[];for(let e=0;e<=i;e++)(o[e]=Array(s+1))[0]=e,r[e]=Array(s+1);const a=o[0];for(let e=1;e<=s;e++)a[e]=e;for(let a=1;a<=i;a++)for(let i=1;i<=s;i++){const s=n(e.item(a-1),t.item(i-1)),c=o[a-1][i]+1,l=o[a][i-1]+1;let h;switch(s){case hi.None:h=o[a-1][i-1];break;case hi.Some:h=o[a-1][i-1]+1;break;case hi.Infinite:h=Number.MAX_VALUE}h{history.pushState(null,"",e),Mi(e,!0)}))}function Ni(e){Oe()||Mi(location.href,!1)}function Pi(e){if(Oe()||e.defaultPrevented)return;const t=e.target;if(t instanceof HTMLFormElement){if(!function(e){const t=e.getAttribute("data-enhance");return"string"==typeof t&&""===t||"true"===(null==t?void 0:t.toLowerCase())}(t))return;e.preventDefault();const n=new URL(t.action),o={method:t.method},r=new FormData(t),i=e.submitter;i&&i.name&&r.append(i.name,i.value),"get"===o.method?(n.search=new URLSearchParams(r).toString(),history.pushState(null,"",n.toString())):o.body=r,Mi(n.toString(),!1,o)}}async function Mi(e,t,n){gi=!0,null==pi||pi.abort(),function(e,t){null==ke||ke(e,t)}(e,t),pi=new AbortController;const o=pi.signal,r=fetch(e,Object.assign({signal:o,mode:"no-cors",headers:{accept:"text/html; blazor-enhanced-nav=on"}},n));let i=null;if(await async function(e,t,n,o){let r;try{if(r=await e,!r.body)return void n(r,"");const t=r.headers.get("ssr-framing");if(!t){const e=await r.text();return void n(r,e)}let o=!0;await r.body.pipeThrough(new TextDecoderStream).pipeThrough(function(e){let t="";return new TransformStream({transform(n,o){if(t+=n,t.indexOf(e,t.length-n.length-e.length)>=0){const n=t.split(e);n.slice(0,-1).forEach((e=>o.enqueue(e))),t=n[n.length-1]}},flush(e){e.enqueue(t)}})}(`\x3c!--${t}--\x3e`)).pipeTo(new WritableStream({write(e){o?(o=!1,n(r,e)):(e=>{const t=document.createRange().createContextualFragment(e);for(;t.firstChild;)document.body.appendChild(t.firstChild)})(e)}}))}catch(e){if("AbortError"===e.name&&t.aborted)return;throw e}}(r,o,((t,o)=>{const r=!(null==n?void 0:n.method)||"get"===n.method,s=t.status>=200&&t.status<300;if("opaque"===t.type){if(r)return void Li(e);throw new Error("Enhanced navigation does not support making a non-GET request to an endpoint that redirects to an external origin. Avoid enabling enhanced navigation for form posts that may perform external redirections.")}if(s&&"allow"!==t.headers.get("blazor-enhanced-nav")){if(r)return void Li(e);throw new Error("Enhanced navigation does not support making a non-GET request to a non-Blazor endpoint. Avoid enabling enhanced navigation for forms that post to a non-Blazor endpoint.")}t.redirected&&(r?history.replaceState(null,"",t.url):history.pushState(null,"",t.url),e=t.url);const a=t.headers.get("blazor-enhanced-nav-redirect-location");if(a)return void location.replace(a);t.redirected||r||!s||(function(e){const t=new URL(e.url),n=new URL(Ri);return t.protocol===n.protocol&&t.host===n.host&&t.port===n.port&&t.pathname===n.pathname}(t)?location.href!==Ri&&history.pushState(null,"",Ri):i=`Cannot perform enhanced form submission that changes the URL (except via a redirection), because then back/forward would not work. Either remove this form's 'action' attribute, or change its method to 'get', or do not mark it as enhanced.\nOld URL: ${location.href}\nNew URL: ${t.url}`),Ri=t.url;const c=t.headers.get("content-type");if((null==c?void 0:c.startsWith("text/html"))&&o){const e=(new DOMParser).parseFromString(o,"text/html");vi(document,e),fi.documentUpdated()}else(null==c?void 0:c.startsWith("text/"))&&o?Ui(o):s||o?r?Li(e):Ui(`Error: ${n.method} request to ${e} returned non-HTML content of type ${c||"unspecified"}.`):Ui(`Error: ${t.status} ${t.statusText}`)})),!o.aborted){const t=e.indexOf("#");if(t>=0){const n=e.substring(t+1),o=document.getElementById(n);null==o||o.scrollIntoView()}if(gi=!1,fi.enhancedNavigationCompleted(),i)throw new Error(i)}}function Ui(e){document.documentElement.textContent=e;const t=document.documentElement.style;t.fontFamily="consolas, monospace",t.whiteSpace="pre-wrap",t.padding="1rem"}function Li(e){history.replaceState(null,"",e+"?"),location.replace(e)}let Bi,Oi=!0;class Fi extends HTMLElement{connectedCallback(){var e;const t=this.parentNode;null===(e=t.parentNode)||void 0===e||e.removeChild(t),t.childNodes.forEach((e=>{if(e instanceof HTMLTemplateElement){const t=e.getAttribute("blazor-component-id");if(t)"true"!==e.getAttribute("enhanced-nav")&&pi||function(e,t){const n=function(e){const t=`bl:${e}`,n=document.createNodeIterator(document,NodeFilter.SHOW_COMMENT);let o=null;for(;(o=n.nextNode())&&o.textContent!==t;);if(!o)return null;const r=`/bl:${e}`;let i=null;for(;(i=n.nextNode())&&i.textContent!==r;);return i?{startMarker:o,endMarker:i}:null}(e);if(n){const{startMarker:e,endMarker:o}=n;if(Oi)vi({startExclusive:e,endExclusive:o},t);else{const n=o.parentNode,r=new Range;for(r.setStart(e,e.textContent.length),r.setEnd(o,0),r.deleteContents();t.childNodes[0];)n.insertBefore(t.childNodes[0],o)}Bi.documentUpdated()}}(t,e.content);else switch(e.getAttribute("type")){case"redirection":const t=Le(e.content.textContent),n="form-post"===e.getAttribute("from");"true"===e.getAttribute("enhanced")&&Pe(t)?(n?history.pushState(null,"",t):history.replaceState(null,"",t),Mi(t,!1)):n?location.assign(t):location.replace(t);break;case"error":Ui(e.content.textContent||"Error")}}}))}}class $i{constructor(e){var t;this._circuitInactivityTimeoutMs=e,this._rootComponentsBySsrComponentId=new Map,this._seenDescriptors=new Set,this._pendingOperationBatches={},this._nextOperationBatchId=1,this._nextSsrComponentId=1,this._didWebAssemblyFailToLoadQuickly=!1,this._isComponentRefreshPending=!1,this.initialComponents=[],t=()=>{this.rootComponentsMayRequireRefresh()},E.push(t)}onAfterRenderBatch(e){e===Bn.Server&&this.circuitMayHaveNoRootComponents()}onDocumentUpdated(){this.rootComponentsMayRequireRefresh()}onEnhancedNavigationCompleted(){this.rootComponentsMayRequireRefresh()}registerComponent(e){if(this._seenDescriptors.has(e))return;"auto"!==e.type&&"webassembly"!==e.type||this.startLoadingWebAssemblyIfNotStarted();const t=this._nextSsrComponentId++;this._seenDescriptors.add(e),this._rootComponentsBySsrComponentId.set(t,{descriptor:e,ssrComponentId:t})}unregisterComponent(e){this._seenDescriptors.delete(e.descriptor),this._rootComponentsBySsrComponentId.delete(e.ssrComponentId),this.circuitMayHaveNoRootComponents()}async startLoadingWebAssemblyIfNotStarted(){if(void 0!==Wr)return;Vr=!0;const e=ti();setTimeout((()=>{ni()||this.onWebAssemblyFailedToLoadQuickly()}),vt._internal.loadWebAssemblyQuicklyTimeout);const t=await Xr;(function(e){if(!e.cacheBootResources)return!1;const t=Hi(e);if(!t)return!1;const n=window.localStorage.getItem(t.key);return t.value===n})(t)||this.onWebAssemblyFailedToLoadQuickly(),await e,function(e){const t=Hi(e);t&&window.localStorage.setItem(t.key,t.value)}(t),this.rootComponentsMayRequireRefresh()}onWebAssemblyFailedToLoadQuickly(){this._didWebAssemblyFailToLoadQuickly||(this._didWebAssemblyFailToLoadQuickly=!0,this.rootComponentsMayRequireRefresh())}startCircutIfNotStarted(){return void 0===Yo?tr(this):!Vo||Vo.isDisposedOrDisposing()?or():void 0}async startWebAssemblyIfNotStarted(){this.startLoadingWebAssemblyIfNotStarted(),void 0===jr&&await Zr(this)}rootComponentsMayRequireRefresh(){this._isComponentRefreshPending||(this._isComponentRefreshPending=!0,setTimeout((()=>{this._isComponentRefreshPending=!1,this.refreshRootComponents(this._rootComponentsBySsrComponentId.values())}),0))}circuitMayHaveNoRootComponents(){if(this.rendererHasExistingOrPendingComponents(Bn.Server,"server","auto"))return clearTimeout(this._circuitInactivityTimeoutId),void(this._circuitInactivityTimeoutId=void 0);void 0===this._circuitInactivityTimeoutId&&(this._circuitInactivityTimeoutId=setTimeout((()=>{this.rendererHasExistingOrPendingComponents(Bn.Server,"server","auto")||(async function(){await(null==Vo?void 0:Vo.dispose())}(),this._circuitInactivityTimeoutId=void 0)}),this._circuitInactivityTimeoutMs))}rendererHasComponents(e){const t=Ae(e);return void 0!==t&&t.getRootComponentCount()>0}rendererHasExistingOrPendingComponents(e,...t){if(this.rendererHasComponents(e))return!0;for(const{descriptor:{type:n},assignedRendererId:o}of this._rootComponentsBySsrComponentId.values()){if(o===e)return!0;if(void 0===o&&-1!==t.indexOf(n))return!0}return!1}refreshRootComponents(e){const t=new Map;for(const n of e){const e=this.determinePendingOperation(n);if(!e)continue;const o=n.assignedRendererId;if(!o)throw new Error("Descriptors must be assigned a renderer ID before getting used as root components");let r=t.get(o);r||(r=[],t.set(o,r)),r.push(e)}for(const[e,n]of t){const t={batchId:this._nextOperationBatchId++,operations:n};this._pendingOperationBatches[t.batchId]=t;const o=JSON.stringify(t);e===Bn.Server?rr(o):this.updateWebAssemblyRootComponents(o)}}updateWebAssemblyRootComponents(e){Kr?(Gr(e),Kr=!1):function(e){if(!jr)throw new Error("Blazor WebAssembly has not started.");if(!vt._internal.updateRootComponents)throw new Error("Blazor WebAssembly has not initialized.");Jr?vt._internal.updateRootComponents(e):async function(e){if(await jr,!vt._internal.updateRootComponents)throw new Error("Blazor WebAssembly has not initialized.");vt._internal.updateRootComponents(e)}(e)}(e)}resolveRendererIdForDescriptor(e){switch("auto"===e.type?this.getAutoRenderMode():e.type){case"server":return this.startCircutIfNotStarted(),Bn.Server;case"webassembly":return this.startWebAssemblyIfNotStarted(),Bn.WebAssembly;case null:return null}}getAutoRenderMode(){return this.rendererHasExistingOrPendingComponents(Bn.WebAssembly,"webassembly")?"webassembly":this.rendererHasExistingOrPendingComponents(Bn.Server,"server")?"server":ni()?"webassembly":this._didWebAssemblyFailToLoadQuickly?"server":null}determinePendingOperation(e){if(t=e.descriptor,document.contains(t.start)){if(void 0===e.assignedRendererId){if(gi||"loading"===document.readyState)return null;const t=this.resolveRendererIdForDescriptor(e.descriptor);return null===t?null:T(t)?(be(e.descriptor.start,!0),e.assignedRendererId=t,e.uniqueIdAtLastUpdate=e.descriptor.uniqueId,{type:"add",ssrComponentId:e.ssrComponentId,marker:Ut(e.descriptor)}):null}return T(e.assignedRendererId)?e.uniqueIdAtLastUpdate===e.descriptor.uniqueId?null:(e.uniqueIdAtLastUpdate=e.descriptor.uniqueId,{type:"update",ssrComponentId:e.ssrComponentId,marker:Ut(e.descriptor)}):null}return e.hasPendingRemoveOperation?null:void 0===e.assignedRendererId?(this.unregisterComponent(e),null):T(e.assignedRendererId)?(be(e.descriptor.start,!1),e.hasPendingRemoveOperation=!0,{type:"remove",ssrComponentId:e.ssrComponentId}):null;var t}resolveRootComponent(e){const t=this._rootComponentsBySsrComponentId.get(e);if(!t)throw new Error(`Could not resolve a root component with SSR component ID '${e}'.`);return t.descriptor}onAfterUpdateRootComponents(e){const t=this._pendingOperationBatches[e];delete this._pendingOperationBatches[e];for(const e of t.operations)switch(e.type){case"remove":{const t=this._rootComponentsBySsrComponentId.get(e.ssrComponentId);t&&this.unregisterComponent(t);break}}}}function Hi(e){var t;const n=null===(t=e.resources)||void 0===t?void 0:t.hash,o=e.mainAssemblyName;return n&&o?{key:`blazor-resource-hash:${o}`,value:n}:null}class Wi{constructor(){this._eventListeners=new Map}static create(e){const t=new Wi;return e.addEventListener=t.addEventListener.bind(t),e.removeEventListener=t.removeEventListener.bind(t),t}addEventListener(e,t){let n=this._eventListeners.get(e);n||(n=new Set,this._eventListeners.set(e,n)),n.add(t)}removeEventListener(e,t){var n;null===(n=this._eventListeners.get(e))||void 0===n||n.delete(t)}dispatchEvent(e,t){const n=this._eventListeners.get(e);if(!n)return;const o={...t,type:e};for(const e of n)e(o)}}let ji,zi=!1;function qi(e){var t,n,o,r;if(zi)throw new Error("Blazor has already started.");zi=!0,null!==(t=(e=e||{}).logLevel)&&void 0!==t||(e.logLevel=yt.Error),vt._internal.loadWebAssemblyQuicklyTimeout=3e3,vt._internal.hotReloadApplied=()=>{Me()&&Ue(location.href,!0)},ji=new $i(null!==(o=null===(n=null==e?void 0:e.ssr)||void 0===n?void 0:n.circuitInactivityTimeoutMs)&&void 0!==o?o:2e3);const i=Wi.create(vt),s={documentUpdated:()=>{ji.onDocumentUpdated(),i.dispatchEvent("enhancedload",{})},enhancedNavigationCompleted(){ji.onEnhancedNavigationCompleted()}};return mi=ji,function(e,t){Bi=t,(null==e?void 0:e.disableDomPreservation)&&(Oi=!1),customElements.define("blazor-ssr-end",Fi)}(null==e?void 0:e.ssr,s),(null===(r=null==e?void 0:e.ssr)||void 0===r?void 0:r.disableDomPreservation)||Di(s),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",Ji.bind(null,e)):Ji(e),Promise.resolve()}function Ji(e){const t=Fo((null==e?void 0:e.circuit)||{});e.circuit=t;const n=async function(e,t){var n;const o=kt(document,Et,"initializers");if(!o)return new qo(!1,t);const r=null!==(n=JSON.parse(atob(o)))&&void 0!==n?n:[],i=new qo(!1,t);return await i.importInitializersAsync(r,[e]),i}(e,new bt(t.logLevel));er(Ki(n,t)),function(e){if($r)throw new Error("WebAssembly options have already been configured.");!async function(e){const t=await e;$r=t,Qr()}(e)}(Ki(n,(null==e?void 0:e.webAssembly)||{})),function(e){const t=Ci(document);for(const e of t)null==mi||mi.registerComponent(e)}(),ji.onDocumentUpdated(),async function(e){const t=await e;await t.invokeAfterStartedCallbacks(vt)}(n)}async function Ki(e,t){return await e,t}vt.start=qi,window.DotNet=e,document&&document.currentScript&&"false"!==document.currentScript.getAttribute("autostart")&&qi()})()})(); \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/dist/Release/blazor.webview.js dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/dist/Release/blazor.webview.js --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/dist/Release/blazor.webview.js 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/dist/Release/blazor.webview.js 2023-11-13 13:20:34.000000000 +0000 @@ -1 +1 @@ -(()=>{"use strict";var e,t,n,r={d:(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};r.d({},{e:()=>Tt}),function(e){const t=[],n="__jsObjectId",r="__dotNetObject",o="__byte[]",a="__dotNetStream",s="__jsStreamReferenceLength";let i,c;class l{constructor(e){this._jsObject=e,this._cachedFunctions=new Map}findFunction(e){const t=this._cachedFunctions.get(e);if(t)return t;let n,r=this._jsObject;if(e.split(".").forEach((t=>{if(!(t in r))throw new Error(`Could not find '${e}' ('${t}' was undefined).`);n=r,r=r[t]})),r instanceof Function)return r=r.bind(n),this._cachedFunctions.set(e,r),r;throw new Error(`The value '${e}' is not a function.`)}getWrappedObject(){return this._jsObject}}const u={0:new l(window)};u[0]._cachedFunctions.set("import",(e=>("string"==typeof e&&e.startsWith("./")&&(e=new URL(e.substr(2),document.baseURI).toString()),import(e))));let d,h=1;function f(e){t.push(e)}function p(e){if(e&&"object"==typeof e){u[h]=new l(e);const t={[n]:h};return h++,t}throw new Error(`Cannot create a JSObjectReference from the value '${e}'.`)}function m(e){let t=-1;if(e instanceof ArrayBuffer&&(e=new Uint8Array(e)),e instanceof Blob)t=e.size;else{if(!(e.buffer instanceof ArrayBuffer))throw new Error("Supplied value is not a typed array or blob.");if(void 0===e.byteLength)throw new Error(`Cannot create a JSStreamReference from the value '${e}' as it doesn't have a byteLength.`);t=e.byteLength}const r={[s]:t};try{const t=p(e);r[n]=t[n]}catch(t){throw new Error(`Cannot create a JSStreamReference from the value '${e}'.`)}return r}function v(e,n){c=e;const r=n?JSON.parse(n,((e,n)=>t.reduce(((t,n)=>n(e,t)),n))):null;return c=void 0,r}function b(){if(void 0===i)throw new Error("No call dispatcher has been set.");if(null===i)throw new Error("There are multiple .NET runtimes present, so a default dispatcher could not be resolved. Use DotNetObject to invoke .NET instance methods.");return i}e.attachDispatcher=function(e){const t=new g(e);return void 0===i?i=t:i&&(i=null),t},e.attachReviver=f,e.invokeMethod=function(e,t,...n){return b().invokeDotNetStaticMethod(e,t,...n)},e.invokeMethodAsync=function(e,t,...n){return b().invokeDotNetStaticMethodAsync(e,t,...n)},e.createJSObjectReference=p,e.createJSStreamReference=m,e.disposeJSObjectReference=function(e){const t=e&&e[n];"number"==typeof t&&E(t)},function(e){e[e.Default=0]="Default",e[e.JSObjectReference=1]="JSObjectReference",e[e.JSStreamReference=2]="JSStreamReference",e[e.JSVoidResult=3]="JSVoidResult"}(d=e.JSCallResultType||(e.JSCallResultType={}));class g{constructor(e){this._dotNetCallDispatcher=e,this._byteArraysToBeRevived=new Map,this._pendingDotNetToJSStreams=new Map,this._pendingAsyncCalls={},this._nextAsyncCallId=1}getDotNetCallDispatcher(){return this._dotNetCallDispatcher}invokeJSFromDotNet(e,t,n,r){const o=v(this,t),a=C(w(e,r)(...o||[]),n);return null==a?null:A(this,a)}beginInvokeJSFromDotNet(e,t,n,r,o){const a=new Promise((e=>{const r=v(this,n);e(w(t,o)(...r||[]))}));e&&a.then((t=>A(this,[e,!0,C(t,r)]))).then((t=>this._dotNetCallDispatcher.endInvokeJSFromDotNet(e,!0,t)),(t=>this._dotNetCallDispatcher.endInvokeJSFromDotNet(e,!1,JSON.stringify([e,!1,y(t)]))))}endInvokeDotNetFromJS(e,t,n){const r=t?v(this,n):new Error(n);this.completePendingCall(parseInt(e,10),t,r)}invokeDotNetStaticMethod(e,t,...n){return this.invokeDotNetMethod(e,t,null,n)}invokeDotNetStaticMethodAsync(e,t,...n){return this.invokeDotNetMethodAsync(e,t,null,n)}invokeDotNetMethod(e,t,n,r){if(this._dotNetCallDispatcher.invokeDotNetFromJS){const o=A(this,r),a=this._dotNetCallDispatcher.invokeDotNetFromJS(e,t,n,o);return a?v(this,a):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeDotNetMethodAsync instead.")}invokeDotNetMethodAsync(e,t,n,r){if(e&&n)throw new Error(`For instance method calls, assemblyName should be null. Received '${e}'.`);const o=this._nextAsyncCallId++,a=new Promise(((e,t)=>{this._pendingAsyncCalls[o]={resolve:e,reject:t}}));try{const a=A(this,r);this._dotNetCallDispatcher.beginInvokeDotNetFromJS(o,e,t,n,a)}catch(e){this.completePendingCall(o,!1,e)}return a}receiveByteArray(e,t){this._byteArraysToBeRevived.set(e,t)}processByteArray(e){const t=this._byteArraysToBeRevived.get(e);return t?(this._byteArraysToBeRevived.delete(e),t):null}supplyDotNetStream(e,t){if(this._pendingDotNetToJSStreams.has(e)){const n=this._pendingDotNetToJSStreams.get(e);this._pendingDotNetToJSStreams.delete(e),n.resolve(t)}else{const n=new D;n.resolve(t),this._pendingDotNetToJSStreams.set(e,n)}}getDotNetStreamPromise(e){let t;if(this._pendingDotNetToJSStreams.has(e))t=this._pendingDotNetToJSStreams.get(e).streamPromise,this._pendingDotNetToJSStreams.delete(e);else{const n=new D;this._pendingDotNetToJSStreams.set(e,n),t=n.streamPromise}return t}completePendingCall(e,t,n){if(!this._pendingAsyncCalls.hasOwnProperty(e))throw new Error(`There is no pending async call with ID ${e}.`);const r=this._pendingAsyncCalls[e];delete this._pendingAsyncCalls[e],t?r.resolve(n):r.reject(n)}}function y(e){return e instanceof Error?`${e.message}\n${e.stack}`:e?e.toString():"null"}function w(e,t){const n=u[t];if(n)return n.findFunction(e);throw new Error(`JS object instance with ID ${t} does not exist (has it been disposed?).`)}function E(e){delete u[e]}e.findJSFunction=w,e.disposeJSObjectReferenceById=E;class S{constructor(e,t){this._id=e,this._callDispatcher=t}invokeMethod(e,...t){return this._callDispatcher.invokeDotNetMethod(null,e,this._id,t)}invokeMethodAsync(e,...t){return this._callDispatcher.invokeDotNetMethodAsync(null,e,this._id,t)}dispose(){this._callDispatcher.invokeDotNetMethodAsync(null,"__Dispose",this._id,null).catch((e=>console.error(e)))}serializeAsArg(){return{[r]:this._id}}}e.DotNetObject=S,f((function(e,t){if(t&&"object"==typeof t){if(t.hasOwnProperty(r))return new S(t[r],c);if(t.hasOwnProperty(n)){const e=t[n],r=u[e];if(r)return r.getWrappedObject();throw new Error(`JS object instance with Id '${e}' does not exist. It may have been disposed.`)}if(t.hasOwnProperty(o)){const e=t[o],n=c.processByteArray(e);if(void 0===n)throw new Error(`Byte array index '${e}' does not exist.`);return n}if(t.hasOwnProperty(a)){const e=t[a],n=c.getDotNetStreamPromise(e);return new I(n)}}return t}));class I{constructor(e){this._streamPromise=e}stream(){return this._streamPromise}async arrayBuffer(){return new Response(await this.stream()).arrayBuffer()}}class D{constructor(){this.streamPromise=new Promise(((e,t)=>{this.resolve=e,this.reject=t}))}}function C(e,t){switch(t){case d.Default:return e;case d.JSObjectReference:return p(e);case d.JSStreamReference:return m(e);case d.JSVoidResult:return null;default:throw new Error(`Invalid JS call result type '${t}'.`)}}let N=0;function A(e,t){N=0,c=e;const n=JSON.stringify(t,k);return c=void 0,n}function k(e,t){if(t instanceof S)return t.serializeAsArg();if(t instanceof Uint8Array){c.getDotNetCallDispatcher().sendByteArray(N,t);const e={[o]:N};return N++,e}return t}}(e||(e={})),function(e){e[e.prependFrame=1]="prependFrame",e[e.removeFrame=2]="removeFrame",e[e.setAttribute=3]="setAttribute",e[e.removeAttribute=4]="removeAttribute",e[e.updateText=5]="updateText",e[e.stepIn=6]="stepIn",e[e.stepOut=7]="stepOut",e[e.updateMarkup=8]="updateMarkup",e[e.permutationListEntry=9]="permutationListEntry",e[e.permutationListEnd=10]="permutationListEnd"}(t||(t={})),function(e){e[e.element=1]="element",e[e.text=2]="text",e[e.attribute=3]="attribute",e[e.component=4]="component",e[e.region=5]="region",e[e.elementReferenceCapture=6]="elementReferenceCapture",e[e.markup=8]="markup",e[e.namedEvent=10]="namedEvent"}(n||(n={}));class o{constructor(e,t){this.componentId=e,this.fieldValue=t}static fromEvent(e,t){const n=t.target;if(n instanceof Element){const t=function(e){return e instanceof HTMLInputElement?e.type&&"checkbox"===e.type.toLowerCase()?{value:e.checked}:{value:e.value}:e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement?{value:e.value}:null}(n);if(t)return new o(e,t.value)}return null}}const a=new Map,s=new Map,i=[];function c(e){return a.get(e)}function l(e){const t=a.get(e);return(null==t?void 0:t.browserEventName)||e}function u(e,t){e.forEach((e=>a.set(e,t)))}function d(e){const t=[];for(let n=0;ne.selected)).map((e=>e.value))}}{const e=function(e){return!!e&&"INPUT"===e.tagName&&"checkbox"===e.getAttribute("type")}(t);return{value:e?!!t.checked:t.value}}}}),u(["copy","cut","paste"],{createEventArgs:e=>({type:e.type})}),u(["drag","dragend","dragenter","dragleave","dragover","dragstart","drop"],{createEventArgs:e=>{return{...h(t=e),dataTransfer:t.dataTransfer?{dropEffect:t.dataTransfer.dropEffect,effectAllowed:t.dataTransfer.effectAllowed,files:Array.from(t.dataTransfer.files).map((e=>e.name)),items:Array.from(t.dataTransfer.items).map((e=>({kind:e.kind,type:e.type}))),types:t.dataTransfer.types}:null};var t}}),u(["focus","blur","focusin","focusout"],{createEventArgs:e=>({type:e.type})}),u(["keydown","keyup","keypress"],{createEventArgs:e=>{return{key:(t=e).key,code:t.code,location:t.location,repeat:t.repeat,ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),u(["contextmenu","click","mouseover","mouseout","mousemove","mousedown","mouseup","mouseleave","mouseenter","dblclick"],{createEventArgs:e=>h(e)}),u(["error"],{createEventArgs:e=>{return{message:(t=e).message,filename:t.filename,lineno:t.lineno,colno:t.colno,type:t.type};var t}}),u(["loadstart","timeout","abort","load","loadend","progress"],{createEventArgs:e=>{return{lengthComputable:(t=e).lengthComputable,loaded:t.loaded,total:t.total,type:t.type};var t}}),u(["touchcancel","touchend","touchmove","touchenter","touchleave","touchstart"],{createEventArgs:e=>{return{detail:(t=e).detail,touches:d(t.touches),targetTouches:d(t.targetTouches),changedTouches:d(t.changedTouches),ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),u(["gotpointercapture","lostpointercapture","pointercancel","pointerdown","pointerenter","pointerleave","pointermove","pointerout","pointerover","pointerup"],{createEventArgs:e=>{return{...h(t=e),pointerId:t.pointerId,width:t.width,height:t.height,pressure:t.pressure,tiltX:t.tiltX,tiltY:t.tiltY,pointerType:t.pointerType,isPrimary:t.isPrimary};var t}}),u(["wheel","mousewheel"],{createEventArgs:e=>{return{...h(t=e),deltaX:t.deltaX,deltaY:t.deltaY,deltaZ:t.deltaZ,deltaMode:t.deltaMode};var t}}),u(["cancel","close","toggle"],{createEventArgs:()=>({})});const f=["date","datetime-local","month","time","week"],p=new Map;let m,v,b=0;const g={async add(e,t,n){if(!n)throw new Error("initialParameters must be an object, even if empty.");const r="__bl-dynamic-root:"+(++b).toString();p.set(r,e);const o=await E().invokeMethodAsync("AddRootComponent",t,r),a=new w(o,v[t]);return await a.setParameters(n),a}};class y{invoke(e){return this._callback(e)}setCallback(t){this._selfJSObjectReference||(this._selfJSObjectReference=e.createJSObjectReference(this)),this._callback=t}getJSObjectReference(){return this._selfJSObjectReference}dispose(){this._selfJSObjectReference&&e.disposeJSObjectReference(this._selfJSObjectReference)}}class w{constructor(e,t){this._jsEventCallbackWrappers=new Map,this._componentId=e;for(const e of t)"eventcallback"===e.type&&this._jsEventCallbackWrappers.set(e.name.toLowerCase(),new y)}setParameters(e){const t={},n=Object.entries(e||{}),r=n.length;for(const[e,r]of n){const n=this._jsEventCallbackWrappers.get(e.toLowerCase());n&&r?(n.setCallback(r),t[e]=n.getJSObjectReference()):t[e]=r}return E().invokeMethodAsync("SetRootComponentParameters",this._componentId,r,t)}async dispose(){if(null!==this._componentId){await E().invokeMethodAsync("RemoveRootComponent",this._componentId),this._componentId=null;for(const e of this._jsEventCallbackWrappers.values())e.dispose()}}}function E(){if(!m)throw new Error("Dynamic root components have not been enabled in this application.");return m}const S=new Map,I=[];let D;const C=new Promise((e=>{D=e}));function N(e,t,n){return k(e,t.eventHandlerId,(()=>A(e).invokeMethodAsync("DispatchEventAsync",t,n)))}function A(e){const t=S.get(e);if(!t)throw new Error(`No interop methods are registered for renderer ${e}`);return t}let k=(e,t,n)=>n();const R=F(["abort","blur","cancel","canplay","canplaythrough","change","close","cuechange","durationchange","emptied","ended","error","focus","load","loadeddata","loadedmetadata","loadend","loadstart","mouseenter","mouseleave","pointerenter","pointerleave","pause","play","playing","progress","ratechange","reset","scroll","seeked","seeking","stalled","submit","suspend","timeupdate","toggle","unload","volumechange","waiting","DOMNodeInsertedIntoDocument","DOMNodeRemovedFromDocument"]),T={submit:!0},_=F(["click","dblclick","mousedown","mousemove","mouseup"]);class O{constructor(e){this.browserRendererId=e,this.afterClickCallbacks=[];const t=++O.nextEventDelegatorId;this.eventsCollectionKey=`_blazorEvents_${t}`,this.eventInfoStore=new x(this.onGlobalEvent.bind(this))}setListener(e,t,n,r){const o=this.getEventHandlerInfosForElement(e,!0),a=o.getHandler(t);if(a)this.eventInfoStore.update(a.eventHandlerId,n);else{const a={element:e,eventName:t,eventHandlerId:n,renderingComponentId:r};this.eventInfoStore.add(a),o.setHandler(t,a)}}getHandler(e){return this.eventInfoStore.get(e)}removeListener(e){const t=this.eventInfoStore.remove(e);if(t){const e=t.element,n=this.getEventHandlerInfosForElement(e,!1);n&&n.removeHandler(t.eventName)}}notifyAfterClick(e){this.afterClickCallbacks.push(e),this.eventInfoStore.addGlobalListener("click")}setStopPropagation(e,t,n){this.getEventHandlerInfosForElement(e,!0).stopPropagation(t,n)}setPreventDefault(e,t,n){this.getEventHandlerInfosForElement(e,!0).preventDefault(t,n)}onGlobalEvent(e){if(!(e.target instanceof Element))return;this.dispatchGlobalEventToAllElements(e.type,e);const t=(n=e.type,s.get(n));var n;t&&t.forEach((t=>this.dispatchGlobalEventToAllElements(t,e))),"click"===e.type&&this.afterClickCallbacks.forEach((t=>t(e)))}dispatchGlobalEventToAllElements(e,t){const n=t.composedPath();let r=n.shift(),a=null,s=!1;const i=Object.prototype.hasOwnProperty.call(R,e);let l=!1;for(;r;){const h=r,f=this.getEventHandlerInfosForElement(h,!1);if(f){const n=f.getHandler(e);if(n&&(u=h,d=t.type,!((u instanceof HTMLButtonElement||u instanceof HTMLInputElement||u instanceof HTMLTextAreaElement||u instanceof HTMLSelectElement)&&Object.prototype.hasOwnProperty.call(_,d)&&u.disabled))){if(!s){const n=c(e);a=(null==n?void 0:n.createEventArgs)?n.createEventArgs(t):{},s=!0}Object.prototype.hasOwnProperty.call(T,t.type)&&t.preventDefault(),N(this.browserRendererId,{eventHandlerId:n.eventHandlerId,eventName:e,eventFieldInfo:o.fromEvent(n.renderingComponentId,t)},a)}f.stopPropagation(e)&&(l=!0),f.preventDefault(e)&&t.preventDefault()}r=i||l?void 0:n.shift()}var u,d}getEventHandlerInfosForElement(e,t){return Object.prototype.hasOwnProperty.call(e,this.eventsCollectionKey)?e[this.eventsCollectionKey]:t?e[this.eventsCollectionKey]=new L:null}}O.nextEventDelegatorId=0;class x{constructor(e){this.globalListener=e,this.infosByEventHandlerId={},this.countByEventName={},i.push(this.handleEventNameAliasAdded.bind(this))}add(e){if(this.infosByEventHandlerId[e.eventHandlerId])throw new Error(`Event ${e.eventHandlerId} is already tracked`);this.infosByEventHandlerId[e.eventHandlerId]=e,this.addGlobalListener(e.eventName)}get(e){return this.infosByEventHandlerId[e]}addGlobalListener(e){if(e=l(e),Object.prototype.hasOwnProperty.call(this.countByEventName,e))this.countByEventName[e]++;else{this.countByEventName[e]=1;const t=Object.prototype.hasOwnProperty.call(R,e);document.addEventListener(e,this.globalListener,t)}}update(e,t){if(Object.prototype.hasOwnProperty.call(this.infosByEventHandlerId,t))throw new Error(`Event ${t} is already tracked`);const n=this.infosByEventHandlerId[e];delete this.infosByEventHandlerId[e],n.eventHandlerId=t,this.infosByEventHandlerId[t]=n}remove(e){const t=this.infosByEventHandlerId[e];if(t){delete this.infosByEventHandlerId[e];const n=l(t.eventName);0==--this.countByEventName[n]&&(delete this.countByEventName[n],document.removeEventListener(n,this.globalListener))}return t}handleEventNameAliasAdded(e,t){if(Object.prototype.hasOwnProperty.call(this.countByEventName,e)){const n=this.countByEventName[e];delete this.countByEventName[e],document.removeEventListener(e,this.globalListener),this.addGlobalListener(t),this.countByEventName[t]+=n-1}}}class L{constructor(){this.handlers={},this.preventDefaultFlags=null,this.stopPropagationFlags=null}getHandler(e){return Object.prototype.hasOwnProperty.call(this.handlers,e)?this.handlers[e]:null}setHandler(e,t){this.handlers[e]=t}removeHandler(e){delete this.handlers[e]}preventDefault(e,t){return void 0!==t&&(this.preventDefaultFlags=this.preventDefaultFlags||{},this.preventDefaultFlags[e]=t),!!this.preventDefaultFlags&&this.preventDefaultFlags[e]}stopPropagation(e,t){return void 0!==t&&(this.stopPropagationFlags=this.stopPropagationFlags||{},this.stopPropagationFlags[e]=t),!!this.stopPropagationFlags&&this.stopPropagationFlags[e]}}function F(e){const t={};return e.forEach((e=>{t[e]=!0})),t}const P=Symbol(),M=Symbol();function B(e,t){if(P in e)return e;const n=[];if(e.childNodes.length>0){if(!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");e.childNodes.forEach((t=>{const r=B(t,!0);r[M]=e,n.push(r)}))}return e[P]=n,e}function j(e){const t=W(e);for(;t.length;)U(e,0)}function H(e,t){const n=document.createComment("!");return J(n,e,t),n}function J(e,t,n){const r=e;let o=e;if(P in e){const t=q(r);if(t!==e){const n=new Range;n.setStartBefore(e),n.setEndAfter(t),o=n.extractContents()}}const a=$(r);if(a){const e=W(a),t=Array.prototype.indexOf.call(e,r);e.splice(t,1),delete r[M]}const s=W(t);if(n0;)U(n,0)}const r=n;r.parentNode.removeChild(r)}function $(e){return e[M]||null}function z(e,t){return W(e)[t]}function K(e){const t=Y(e);return"http://www.w3.org/2000/svg"===t.namespaceURI&&"foreignObject"!==t.tagName}function W(e){return e[P]}function V(e){const t=W($(e));return t[Array.prototype.indexOf.call(t,e)+1]||null}function X(e,t){const n=W(e);t.forEach((e=>{e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=q(e.moveRangeStart)})),t.forEach((t=>{const r=document.createComment("marker");t.moveToBeforeMarker=r;const o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):G(r,e)})),t.forEach((e=>{const t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd;let a=r;for(;a;){const e=a.nextSibling;if(n.insertBefore(a,t),a===o)break;a=e}n.removeChild(t)})),t.forEach((e=>{n[e.toSiblingIndex]=e.moveRangeStart}))}function Y(e){if(e instanceof Element||e instanceof DocumentFragment)return e;if(e instanceof Comment)return e.parentNode;throw new Error("Not a valid logical element")}function G(e,t){if(t instanceof Element||t instanceof DocumentFragment)t.appendChild(e);else{if(!(t instanceof Comment))throw new Error(`Cannot append node because the parent is not a valid logical element. Parent: ${t}`);{const n=V(t);n?n.parentNode.insertBefore(e,n):G(e,$(t))}}}function q(e){if(e instanceof Element||e instanceof DocumentFragment)return e;const t=V(e);if(t)return t.previousSibling;{const t=$(e);return t instanceof Element||t instanceof DocumentFragment?t.lastChild:q(t)}}function Z(e){return`_bl_${e}`}Symbol();const Q="__internalId";e.attachReviver(((e,t)=>t&&"object"==typeof t&&Object.prototype.hasOwnProperty.call(t,Q)&&"string"==typeof t[Q]?function(e){const t=`[${Z(e)}]`;return document.querySelector(t)}(t[Q]):t));const ee="_blazorDeferredValue";function te(e){return"select-multiple"===e.type}function ne(e,t){e.value=t||""}function re(e,t){e instanceof HTMLSelectElement?te(e)?function(e,t){t||(t=[]);for(let n=0;n{Ie()&&function(e,t){if(0!==e.button||function(e){return e.ctrlKey||e.shiftKey||e.altKey||e.metaKey}(e))return;if(e.defaultPrevented)return;const n=function(e){const t=!window._blazorDisableComposedPath&&e.composedPath&&e.composedPath();if(t){for(let e=0;edocument.baseURI,getLocationHref:()=>location.href,scrollToElement:xe};function xe(e){const t=document.getElementById(e);return!!t&&(t.scrollIntoView(),!0)}function Le(e,t,n=!1){const r=Ee(e);!t.forceLoad&&ge(r)?Ue()?Fe(r,!1,t.replaceHistoryEntry,t.historyEntryState,n):we(r,t.replaceHistoryEntry):function(e,t){if(location.href===e){const t=e+"?";history.replaceState(null,"",t),location.replace(e)}else t?location.replace(e):location.href=e}(e,t.replaceHistoryEntry)}async function Fe(e,t,n,r=void 0,o=!1){if(Be(),function(e){const t=e.indexOf("#");return t>-1&&location.href.replace(location.hash,"")===e.substring(0,t)}(e))!function(e,t,n){Pe(e,t,n);const r=e.indexOf("#");r!==e.length-1&&xe(e.substring(r+1))}(e,n,r);else{if(!o&&Ce&&!await je(e,r,t))return;ve=!0,Pe(e,n,r),await He(t)}}function Pe(e,t,n=void 0){t?history.replaceState({userState:n,_index:Ne},"",e):(Ne++,history.pushState({userState:n,_index:Ne},"",e))}function Me(e){return new Promise((t=>{const n=Te;Te=()=>{Te=n,t()},history.go(e)}))}function Be(){_e&&(_e(!1),_e=null)}function je(e,t,n){return new Promise((r=>{Be(),Re?(Ae++,_e=r,Re(Ae,e,t,n)):r(!1)}))}async function He(e){var t;ke&&await ke(location.href,null===(t=history.state)||void 0===t?void 0:t.userState,e)}async function Je(e){var t,n;Te&&Ue()&&await Te(e),Ne=null!==(n=null===(t=history.state)||void 0===t?void 0:t._index)&&void 0!==n?n:0}function Ue(){return Ie()||!ye()}const $e={focus:function(e,t){if(e instanceof HTMLElement)e.focus({preventScroll:t});else{if(!(e instanceof SVGElement))throw new Error("Unable to focus an invalid element.");if(!e.hasAttribute("tabindex"))throw new Error("Unable to focus an SVG element that does not have a tabindex.");e.focus({preventScroll:t})}},focusBySelector:function(e,t){const n=document.querySelector(e);n&&(n.hasAttribute("tabindex")||(n.tabIndex=-1),n.focus({preventScroll:!0}))}},ze={init:function(e,t,n,r=50){const o=We(t);(o||document.documentElement).style.overflowAnchor="none";const a=document.createRange();h(n.parentElement)&&(t.style.display="table-row",n.style.display="table-row");const s=new IntersectionObserver((function(r){r.forEach((r=>{var o;if(!r.isIntersecting)return;a.setStartAfter(t),a.setEndBefore(n);const s=a.getBoundingClientRect().height,i=null===(o=r.rootBounds)||void 0===o?void 0:o.height;r.target===t?e.invokeMethodAsync("OnSpacerBeforeVisible",r.intersectionRect.top-r.boundingClientRect.top,s,i):r.target===n&&n.offsetHeight>0&&e.invokeMethodAsync("OnSpacerAfterVisible",r.boundingClientRect.bottom-r.intersectionRect.bottom,s,i)}))}),{root:o,rootMargin:`${r}px`});s.observe(t),s.observe(n);const i=d(t),c=d(n),{observersByDotNetObjectId:l,id:u}=Ve(e);function d(e){const t={attributes:!0},n=new MutationObserver(((n,r)=>{h(e.parentElement)&&(r.disconnect(),e.style.display="table-row",r.observe(e,t)),s.unobserve(e),s.observe(e)}));return n.observe(e,t),n}function h(e){return null!==e&&(e instanceof HTMLTableElement&&""===e.style.display||"table"===e.style.display||e instanceof HTMLTableSectionElement&&""===e.style.display||"table-row-group"===e.style.display)}l[u]={intersectionObserver:s,mutationObserverBefore:i,mutationObserverAfter:c}},dispose:function(e){const{observersByDotNetObjectId:t,id:n}=Ve(e),r=t[n];r&&(r.intersectionObserver.disconnect(),r.mutationObserverBefore.disconnect(),r.mutationObserverAfter.disconnect(),e.dispose(),delete t[n])}},Ke=Symbol();function We(e){return e&&e!==document.body&&e!==document.documentElement?"visible"!==getComputedStyle(e).overflowY?e:We(e.parentElement):null}function Ve(e){var t;const n=e._callDispatcher,r=e._id;return null!==(t=n[Ke])&&void 0!==t||(n[Ke]={}),{observersByDotNetObjectId:n[Ke],id:r}}const Xe={getAndRemoveExistingTitle:function(){var e;const t=document.head?document.head.getElementsByTagName("title"):[];if(0===t.length)return null;let n=null;for(let r=t.length-1;r>=0;r--){const o=t[r],a=o.previousSibling;a instanceof Comment&&null!==$(a)||(null===n&&(n=o.textContent),null===(e=o.parentNode)||void 0===e||e.removeChild(o))}return n}},Ye={init:function(e,t){t._blazorInputFileNextFileId=0,t.addEventListener("click",(function(){t.value=""})),t.addEventListener("change",(function(){t._blazorFilesById={};const n=Array.prototype.map.call(t.files,(function(e){const n={id:++t._blazorInputFileNextFileId,lastModified:new Date(e.lastModified).toISOString(),name:e.name,size:e.size,contentType:e.type,readPromise:void 0,arrayBuffer:void 0,blob:e};return t._blazorFilesById[n.id]=n,n}));e.invokeMethodAsync("NotifyChange",n)}))},toImageFile:async function(e,t,n,r,o){const a=Ge(e,t),s=await new Promise((function(e){const t=new Image;t.onload=function(){URL.revokeObjectURL(t.src),e(t)},t.onerror=function(){t.onerror=null,URL.revokeObjectURL(t.src)},t.src=URL.createObjectURL(a.blob)})),i=await new Promise((function(e){var t;const a=Math.min(1,r/s.width),i=Math.min(1,o/s.height),c=Math.min(a,i),l=document.createElement("canvas");l.width=Math.round(s.width*c),l.height=Math.round(s.height*c),null===(t=l.getContext("2d"))||void 0===t||t.drawImage(s,0,0,l.width,l.height),l.toBlob(e,n)})),c={id:++e._blazorInputFileNextFileId,lastModified:a.lastModified,name:a.name,size:(null==i?void 0:i.size)||0,contentType:n,blob:i||a.blob};return e._blazorFilesById[c.id]=c,c},readFileData:async function(e,t){return Ge(e,t).blob}};function Ge(e,t){const n=e._blazorFilesById[t];if(!n)throw new Error(`There is no file with ID ${t}. The file list may have changed. See https://aka.ms/aspnet/blazor-input-file-multiple-selections.`);return n}const qe=new Set,Ze={enableNavigationPrompt:function(e){0===qe.size&&window.addEventListener("beforeunload",Qe),qe.add(e)},disableNavigationPrompt:function(e){qe.delete(e),0===qe.size&&window.removeEventListener("beforeunload",Qe)}};function Qe(e){e.preventDefault(),e.returnValue=!0}const et=new Map,tt={navigateTo:function(e,t,n=!1){Le(e,t instanceof Object?t:{forceLoad:t,replaceHistoryEntry:n})},registerCustomEventType:function(e,t){if(!t)throw new Error("The options parameter is required.");if(a.has(e))throw new Error(`The event '${e}' is already registered.`);if(t.browserEventName){const n=s.get(t.browserEventName);n?n.push(e):s.set(t.browserEventName,[e]),i.forEach((n=>n(e,t.browserEventName)))}a.set(e,t)},rootComponents:g,runtime:{},_internal:{navigationManager:Oe,domWrapper:$e,Virtualize:ze,PageTitle:Xe,InputFile:Ye,NavigationLock:Ze,getJSDataStreamChunk:async function(e,t,n){return e instanceof Blob?await async function(e,t,n){const r=e.slice(t,t+n),o=await r.arrayBuffer();return new Uint8Array(o)}(e,t,n):function(e,t,n){return new Uint8Array(e.buffer,e.byteOffset+t,n)}(e,t,n)},attachWebRendererInterop:function(t,n,r,o){if(S.has(t))throw new Error(`Interop methods are already registered for renderer ${t}`);S.set(t,n),Object.keys(r).length>0&&function(t,n,r){if(m)throw new Error("Dynamic root components have already been enabled.");m=t,v=n;for(const[t,o]of Object.entries(r)){const r=e.findJSFunction(t,0);for(const e of o)r(e,n[e])}}(A(t),r,o),D(),function(e){for(const t of I)t(e)}(t)}}};window.Blazor=tt;let nt=!1;const rt="function"==typeof TextDecoder?new TextDecoder("utf-8"):null,ot=rt?rt.decode.bind(rt):function(e){let t=0;const n=e.length,r=[],o=[];for(;t65535&&(o-=65536,r.push(o>>>10&1023|55296),o=56320|1023&o),r.push(o)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")},at=Math.pow(2,32),st=Math.pow(2,21)-1;function it(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24}function ct(e,t){return e[t]+(e[t+1]<<8)+(e[t+2]<<16)+(e[t+3]<<24>>>0)}function lt(e,t){const n=ct(e,t+4);if(n>st)throw new Error(`Cannot read uint64 with high order part ${n}, because the result would exceed Number.MAX_SAFE_INTEGER.`);return n*at+ct(e,t)}class ut{constructor(e){this.batchData=e;const t=new pt(e);this.arrayRangeReader=new mt(e),this.arrayBuilderSegmentReader=new vt(e),this.diffReader=new dt(e),this.editReader=new ht(e,t),this.frameReader=new ft(e,t)}updatedComponents(){return it(this.batchData,this.batchData.length-20)}referenceFrames(){return it(this.batchData,this.batchData.length-16)}disposedComponentIds(){return it(this.batchData,this.batchData.length-12)}disposedEventHandlerIds(){return it(this.batchData,this.batchData.length-8)}updatedComponentsEntry(e,t){const n=e+4*t;return it(this.batchData,n)}referenceFramesEntry(e,t){return e+20*t}disposedComponentIdsEntry(e,t){const n=e+4*t;return it(this.batchData,n)}disposedEventHandlerIdsEntry(e,t){const n=e+8*t;return lt(this.batchData,n)}}class dt{constructor(e){this.batchDataUint8=e}componentId(e){return it(this.batchDataUint8,e)}edits(e){return e+4}editsEntry(e,t){return e+16*t}}class ht{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}editType(e){return it(this.batchDataUint8,e)}siblingIndex(e){return it(this.batchDataUint8,e+4)}newTreeIndex(e){return it(this.batchDataUint8,e+8)}moveToSiblingIndex(e){return it(this.batchDataUint8,e+8)}removedAttributeName(e){const t=it(this.batchDataUint8,e+12);return this.stringReader.readString(t)}}class ft{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}frameType(e){return it(this.batchDataUint8,e)}subtreeLength(e){return it(this.batchDataUint8,e+4)}elementReferenceCaptureId(e){const t=it(this.batchDataUint8,e+4);return this.stringReader.readString(t)}componentId(e){return it(this.batchDataUint8,e+8)}elementName(e){const t=it(this.batchDataUint8,e+8);return this.stringReader.readString(t)}textContent(e){const t=it(this.batchDataUint8,e+4);return this.stringReader.readString(t)}markupContent(e){const t=it(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeName(e){const t=it(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeValue(e){const t=it(this.batchDataUint8,e+8);return this.stringReader.readString(t)}attributeEventHandlerId(e){return lt(this.batchDataUint8,e+12)}}class pt{constructor(e){this.batchDataUint8=e,this.stringTableStartIndex=it(e,e.length-4)}readString(e){if(-1===e)return null;{const n=it(this.batchDataUint8,this.stringTableStartIndex+4*e),r=function(e,t){let n=0,r=0;for(let o=0;o<4;o++){const a=e[t+o];if(n|=(127&a)<async function(e,n){const r=function(e){const t=document.baseURI;return t.endsWith("/")?`${t}${e}`:`${t}/${e}`}(n),o=await import(r);if(void 0===o)return;const{beforeStart:a,afterStarted:s}=o;return s&&e.afterStartedCallbacks.push(s),a?a(...t):void 0}(this,e))))}async invokeAfterStartedCallbacks(e){await C,await Promise.all(this.afterStartedCallbacks.map((t=>t(e))))}}let Tt,_t=!1;async function Ot(){if(_t)throw new Error("Blazor has already started.");_t=!0,Tt=e.attachDispatcher({beginInvokeDotNetFromJS:wt,endInvokeJSFromDotNet:Et,sendByteArray:St});const t=await async function(){const e=await fetch("_framework/blazor.modules.json",{method:"GET",credentials:"include",cache:"no-cache"}),t=await e.json(),n=new Rt;return await n.importInitializersAsync(t,[]),n}();(function(){const e={AttachToDocument:(e,t)=>{!function(e,t,n){const r="::before";let o=!1;if(e.endsWith("::after"))e=e.slice(0,-7),o=!0;else if(e.endsWith(r))throw new Error(`The '${r}' selector is not supported.`);const a=function(e){const t=p.get(e);if(t)return p.delete(e),t}(e)||document.querySelector(e);if(!a)throw new Error(`Could not find any element matching selector '${e}'.`);!function(e,t,n,r){let o=fe[e];o||(o=new le(e),fe[e]=o),o.attachRootComponentToLogicalElement(n,t,r)}(n||0,B(a,!0),t,o)}(t,e,Nt.WebView)},RenderBatch:(e,t)=>{try{const n=kt(t);(function(e,t){const n=fe[e];if(!n)throw new Error(`There is no browser renderer with ID ${e}.`);const r=t.arrayRangeReader,o=t.updatedComponents(),a=r.values(o),s=r.count(o),i=t.referenceFrames(),c=r.values(i),l=t.diffReader;for(let e=0;e{gt=!0,console.error(`${e}\n${t}`),function(){const e=document.querySelector("#blazor-error-ui");e&&(e.style.display="block"),nt||(nt=!0,document.querySelectorAll("#blazor-error-ui .reload").forEach((e=>{e.onclick=function(e){location.reload(),e.preventDefault()}})),document.querySelectorAll("#blazor-error-ui .dismiss").forEach((e=>{e.onclick=function(e){const t=document.querySelector("#blazor-error-ui");t&&(t.style.display="none"),e.preventDefault()}})))}()},BeginInvokeJS:Tt.beginInvokeJSFromDotNet.bind(Tt),EndInvokeDotNet:Tt.endInvokeDotNetFromJS.bind(Tt),SendByteArrayToJS:At,Navigate:Oe.navigateTo,Refresh:Oe.refresh,SetHasLocationChangingListeners:Oe.setHasLocationChangingListeners,EndLocationChanging:Oe.endLocationChanging};window.external.receiveMessage((t=>{const n=function(e){if(gt||!e||!e.startsWith(bt))return null;const t=e.substring(bt.length),[n,...r]=JSON.parse(t);return{messageType:n,args:r}}(t);if(n){if(!Object.prototype.hasOwnProperty.call(e,n.messageType))throw new Error(`Unsupported IPC message type '${n.messageType}'`);e[n.messageType].apply(null,n.args)}}))})(),tt._internal.receiveWebViewDotNetDataStream=xt,Oe.enableNavigationInterception(),Oe.listenForNavigationEvents(It,Dt),Ct("AttachPage",Oe.getBaseURI(),Oe.getLocationHref()),await t.invokeAfterStartedCallbacks(tt)}function xt(e,t,n,r){!function(e,t,n,r,o){let a=et.get(t);if(!a){const n=new ReadableStream({start(e){et.set(t,e),a=e}});e.supplyDotNetStream(t,n)}o?(a.error(o),et.delete(t)):0===r?(a.close(),et.delete(t)):a.enqueue(n.length===r?n:n.subarray(0,r))}(Tt,e,t,n,r)}tt.start=Ot,window.DotNet=e,document&&document.currentScript&&"false"!==document.currentScript.getAttribute("autostart")&&Ot()})(); \ No newline at end of file +(()=>{"use strict";var e,t,n,r={d:(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};r.d({},{e:()=>Ot}),function(e){const t=[],n="__jsObjectId",r="__dotNetObject",o="__byte[]",a="__dotNetStream",i="__jsStreamReferenceLength";let s,c;class l{constructor(e){this._jsObject=e,this._cachedFunctions=new Map}findFunction(e){const t=this._cachedFunctions.get(e);if(t)return t;let n,r=this._jsObject;if(e.split(".").forEach((t=>{if(!(t in r))throw new Error(`Could not find '${e}' ('${t}' was undefined).`);n=r,r=r[t]})),r instanceof Function)return r=r.bind(n),this._cachedFunctions.set(e,r),r;throw new Error(`The value '${e}' is not a function.`)}getWrappedObject(){return this._jsObject}}const u={0:new l(window)};u[0]._cachedFunctions.set("import",(e=>("string"==typeof e&&e.startsWith("./")&&(e=new URL(e.substr(2),document.baseURI).toString()),import(e))));let d,h=1;function f(e){t.push(e)}function m(e){if(e&&"object"==typeof e){u[h]=new l(e);const t={[n]:h};return h++,t}throw new Error(`Cannot create a JSObjectReference from the value '${e}'.`)}function p(e){let t=-1;if(e instanceof ArrayBuffer&&(e=new Uint8Array(e)),e instanceof Blob)t=e.size;else{if(!(e.buffer instanceof ArrayBuffer))throw new Error("Supplied value is not a typed array or blob.");if(void 0===e.byteLength)throw new Error(`Cannot create a JSStreamReference from the value '${e}' as it doesn't have a byteLength.`);t=e.byteLength}const r={[i]:t};try{const t=m(e);r[n]=t[n]}catch(t){throw new Error(`Cannot create a JSStreamReference from the value '${e}'.`)}return r}function b(e,n){c=e;const r=n?JSON.parse(n,((e,n)=>t.reduce(((t,n)=>n(e,t)),n))):null;return c=void 0,r}function v(){if(void 0===s)throw new Error("No call dispatcher has been set.");if(null===s)throw new Error("There are multiple .NET runtimes present, so a default dispatcher could not be resolved. Use DotNetObject to invoke .NET instance methods.");return s}e.attachDispatcher=function(e){const t=new g(e);return void 0===s?s=t:s&&(s=null),t},e.attachReviver=f,e.invokeMethod=function(e,t,...n){return v().invokeDotNetStaticMethod(e,t,...n)},e.invokeMethodAsync=function(e,t,...n){return v().invokeDotNetStaticMethodAsync(e,t,...n)},e.createJSObjectReference=m,e.createJSStreamReference=p,e.disposeJSObjectReference=function(e){const t=e&&e[n];"number"==typeof t&&E(t)},function(e){e[e.Default=0]="Default",e[e.JSObjectReference=1]="JSObjectReference",e[e.JSStreamReference=2]="JSStreamReference",e[e.JSVoidResult=3]="JSVoidResult"}(d=e.JSCallResultType||(e.JSCallResultType={}));class g{constructor(e){this._dotNetCallDispatcher=e,this._byteArraysToBeRevived=new Map,this._pendingDotNetToJSStreams=new Map,this._pendingAsyncCalls={},this._nextAsyncCallId=1}getDotNetCallDispatcher(){return this._dotNetCallDispatcher}invokeJSFromDotNet(e,t,n,r){const o=b(this,t),a=D(w(e,r)(...o||[]),n);return null==a?null:N(this,a)}beginInvokeJSFromDotNet(e,t,n,r,o){const a=new Promise((e=>{const r=b(this,n);e(w(t,o)(...r||[]))}));e&&a.then((t=>N(this,[e,!0,D(t,r)]))).then((t=>this._dotNetCallDispatcher.endInvokeJSFromDotNet(e,!0,t)),(t=>this._dotNetCallDispatcher.endInvokeJSFromDotNet(e,!1,JSON.stringify([e,!1,y(t)]))))}endInvokeDotNetFromJS(e,t,n){const r=t?b(this,n):new Error(n);this.completePendingCall(parseInt(e,10),t,r)}invokeDotNetStaticMethod(e,t,...n){return this.invokeDotNetMethod(e,t,null,n)}invokeDotNetStaticMethodAsync(e,t,...n){return this.invokeDotNetMethodAsync(e,t,null,n)}invokeDotNetMethod(e,t,n,r){if(this._dotNetCallDispatcher.invokeDotNetFromJS){const o=N(this,r),a=this._dotNetCallDispatcher.invokeDotNetFromJS(e,t,n,o);return a?b(this,a):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeDotNetMethodAsync instead.")}invokeDotNetMethodAsync(e,t,n,r){if(e&&n)throw new Error(`For instance method calls, assemblyName should be null. Received '${e}'.`);const o=this._nextAsyncCallId++,a=new Promise(((e,t)=>{this._pendingAsyncCalls[o]={resolve:e,reject:t}}));try{const a=N(this,r);this._dotNetCallDispatcher.beginInvokeDotNetFromJS(o,e,t,n,a)}catch(e){this.completePendingCall(o,!1,e)}return a}receiveByteArray(e,t){this._byteArraysToBeRevived.set(e,t)}processByteArray(e){const t=this._byteArraysToBeRevived.get(e);return t?(this._byteArraysToBeRevived.delete(e),t):null}supplyDotNetStream(e,t){if(this._pendingDotNetToJSStreams.has(e)){const n=this._pendingDotNetToJSStreams.get(e);this._pendingDotNetToJSStreams.delete(e),n.resolve(t)}else{const n=new C;n.resolve(t),this._pendingDotNetToJSStreams.set(e,n)}}getDotNetStreamPromise(e){let t;if(this._pendingDotNetToJSStreams.has(e))t=this._pendingDotNetToJSStreams.get(e).streamPromise,this._pendingDotNetToJSStreams.delete(e);else{const n=new C;this._pendingDotNetToJSStreams.set(e,n),t=n.streamPromise}return t}completePendingCall(e,t,n){if(!this._pendingAsyncCalls.hasOwnProperty(e))throw new Error(`There is no pending async call with ID ${e}.`);const r=this._pendingAsyncCalls[e];delete this._pendingAsyncCalls[e],t?r.resolve(n):r.reject(n)}}function y(e){return e instanceof Error?`${e.message}\n${e.stack}`:e?e.toString():"null"}function w(e,t){const n=u[t];if(n)return n.findFunction(e);throw new Error(`JS object instance with ID ${t} does not exist (has it been disposed?).`)}function E(e){delete u[e]}e.findJSFunction=w,e.disposeJSObjectReferenceById=E;class S{constructor(e,t){this._id=e,this._callDispatcher=t}invokeMethod(e,...t){return this._callDispatcher.invokeDotNetMethod(null,e,this._id,t)}invokeMethodAsync(e,...t){return this._callDispatcher.invokeDotNetMethodAsync(null,e,this._id,t)}dispose(){this._callDispatcher.invokeDotNetMethodAsync(null,"__Dispose",this._id,null).catch((e=>console.error(e)))}serializeAsArg(){return{[r]:this._id}}}e.DotNetObject=S,f((function(e,t){if(t&&"object"==typeof t){if(t.hasOwnProperty(r))return new S(t[r],c);if(t.hasOwnProperty(n)){const e=t[n],r=u[e];if(r)return r.getWrappedObject();throw new Error(`JS object instance with Id '${e}' does not exist. It may have been disposed.`)}if(t.hasOwnProperty(o)){const e=t[o],n=c.processByteArray(e);if(void 0===n)throw new Error(`Byte array index '${e}' does not exist.`);return n}if(t.hasOwnProperty(a)){const e=t[a],n=c.getDotNetStreamPromise(e);return new I(n)}}return t}));class I{constructor(e){this._streamPromise=e}stream(){return this._streamPromise}async arrayBuffer(){return new Response(await this.stream()).arrayBuffer()}}class C{constructor(){this.streamPromise=new Promise(((e,t)=>{this.resolve=e,this.reject=t}))}}function D(e,t){switch(t){case d.Default:return e;case d.JSObjectReference:return m(e);case d.JSStreamReference:return p(e);case d.JSVoidResult:return null;default:throw new Error(`Invalid JS call result type '${t}'.`)}}let A=0;function N(e,t){A=0,c=e;const n=JSON.stringify(t,k);return c=void 0,n}function k(e,t){if(t instanceof S)return t.serializeAsArg();if(t instanceof Uint8Array){c.getDotNetCallDispatcher().sendByteArray(A,t);const e={[o]:A};return A++,e}return t}}(e||(e={})),function(e){e[e.prependFrame=1]="prependFrame",e[e.removeFrame=2]="removeFrame",e[e.setAttribute=3]="setAttribute",e[e.removeAttribute=4]="removeAttribute",e[e.updateText=5]="updateText",e[e.stepIn=6]="stepIn",e[e.stepOut=7]="stepOut",e[e.updateMarkup=8]="updateMarkup",e[e.permutationListEntry=9]="permutationListEntry",e[e.permutationListEnd=10]="permutationListEnd"}(t||(t={})),function(e){e[e.element=1]="element",e[e.text=2]="text",e[e.attribute=3]="attribute",e[e.component=4]="component",e[e.region=5]="region",e[e.elementReferenceCapture=6]="elementReferenceCapture",e[e.markup=8]="markup",e[e.namedEvent=10]="namedEvent"}(n||(n={}));class o{constructor(e,t){this.componentId=e,this.fieldValue=t}static fromEvent(e,t){const n=t.target;if(n instanceof Element){const t=function(e){return e instanceof HTMLInputElement?e.type&&"checkbox"===e.type.toLowerCase()?{value:e.checked}:{value:e.value}:e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement?{value:e.value}:null}(n);if(t)return new o(e,t.value)}return null}}const a=new Map,i=new Map,s=[];function c(e){return a.get(e)}function l(e){const t=a.get(e);return(null==t?void 0:t.browserEventName)||e}function u(e,t){e.forEach((e=>a.set(e,t)))}function d(e){const t=[];for(let n=0;ne.selected)).map((e=>e.value))}}{const e=function(e){return!!e&&"INPUT"===e.tagName&&"checkbox"===e.getAttribute("type")}(t);return{value:e?!!t.checked:t.value}}}}),u(["copy","cut","paste"],{createEventArgs:e=>({type:e.type})}),u(["drag","dragend","dragenter","dragleave","dragover","dragstart","drop"],{createEventArgs:e=>{return{...h(t=e),dataTransfer:t.dataTransfer?{dropEffect:t.dataTransfer.dropEffect,effectAllowed:t.dataTransfer.effectAllowed,files:Array.from(t.dataTransfer.files).map((e=>e.name)),items:Array.from(t.dataTransfer.items).map((e=>({kind:e.kind,type:e.type}))),types:t.dataTransfer.types}:null};var t}}),u(["focus","blur","focusin","focusout"],{createEventArgs:e=>({type:e.type})}),u(["keydown","keyup","keypress"],{createEventArgs:e=>{return{key:(t=e).key,code:t.code,location:t.location,repeat:t.repeat,ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),u(["contextmenu","click","mouseover","mouseout","mousemove","mousedown","mouseup","mouseleave","mouseenter","dblclick"],{createEventArgs:e=>h(e)}),u(["error"],{createEventArgs:e=>{return{message:(t=e).message,filename:t.filename,lineno:t.lineno,colno:t.colno,type:t.type};var t}}),u(["loadstart","timeout","abort","load","loadend","progress"],{createEventArgs:e=>{return{lengthComputable:(t=e).lengthComputable,loaded:t.loaded,total:t.total,type:t.type};var t}}),u(["touchcancel","touchend","touchmove","touchenter","touchleave","touchstart"],{createEventArgs:e=>{return{detail:(t=e).detail,touches:d(t.touches),targetTouches:d(t.targetTouches),changedTouches:d(t.changedTouches),ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),u(["gotpointercapture","lostpointercapture","pointercancel","pointerdown","pointerenter","pointerleave","pointermove","pointerout","pointerover","pointerup"],{createEventArgs:e=>{return{...h(t=e),pointerId:t.pointerId,width:t.width,height:t.height,pressure:t.pressure,tiltX:t.tiltX,tiltY:t.tiltY,pointerType:t.pointerType,isPrimary:t.isPrimary};var t}}),u(["wheel","mousewheel"],{createEventArgs:e=>{return{...h(t=e),deltaX:t.deltaX,deltaY:t.deltaY,deltaZ:t.deltaZ,deltaMode:t.deltaMode};var t}}),u(["cancel","close","toggle"],{createEventArgs:()=>({})});const f=["date","datetime-local","month","time","week"],m=new Map;let p,b,v=0;const g={async add(e,t,n){if(!n)throw new Error("initialParameters must be an object, even if empty.");const r="__bl-dynamic-root:"+(++v).toString();m.set(r,e);const o=await E().invokeMethodAsync("AddRootComponent",t,r),a=new w(o,b[t]);return await a.setParameters(n),a}};class y{invoke(e){return this._callback(e)}setCallback(t){this._selfJSObjectReference||(this._selfJSObjectReference=e.createJSObjectReference(this)),this._callback=t}getJSObjectReference(){return this._selfJSObjectReference}dispose(){this._selfJSObjectReference&&e.disposeJSObjectReference(this._selfJSObjectReference)}}class w{constructor(e,t){this._jsEventCallbackWrappers=new Map,this._componentId=e;for(const e of t)"eventcallback"===e.type&&this._jsEventCallbackWrappers.set(e.name.toLowerCase(),new y)}setParameters(e){const t={},n=Object.entries(e||{}),r=n.length;for(const[e,r]of n){const n=this._jsEventCallbackWrappers.get(e.toLowerCase());n&&r?(n.setCallback(r),t[e]=n.getJSObjectReference()):t[e]=r}return E().invokeMethodAsync("SetRootComponentParameters",this._componentId,r,t)}async dispose(){if(null!==this._componentId){await E().invokeMethodAsync("RemoveRootComponent",this._componentId),this._componentId=null;for(const e of this._jsEventCallbackWrappers.values())e.dispose()}}}function E(){if(!p)throw new Error("Dynamic root components have not been enabled in this application.");return p}const S=new Map,I=[],C=new Map;function D(e,t,n){return N(e,t.eventHandlerId,(()=>A(e).invokeMethodAsync("DispatchEventAsync",t,n)))}function A(e){const t=S.get(e);if(!t)throw new Error(`No interop methods are registered for renderer ${e}`);return t}let N=(e,t,n)=>n();const k=x(["abort","blur","cancel","canplay","canplaythrough","change","close","cuechange","durationchange","emptied","ended","error","focus","load","loadeddata","loadedmetadata","loadend","loadstart","mouseenter","mouseleave","pointerenter","pointerleave","pause","play","playing","progress","ratechange","reset","scroll","seeked","seeking","stalled","submit","suspend","timeupdate","toggle","unload","volumechange","waiting","DOMNodeInsertedIntoDocument","DOMNodeRemovedFromDocument"]),R={submit:!0},T=x(["click","dblclick","mousedown","mousemove","mouseup"]);class _{constructor(e){this.browserRendererId=e,this.afterClickCallbacks=[];const t=++_.nextEventDelegatorId;this.eventsCollectionKey=`_blazorEvents_${t}`,this.eventInfoStore=new O(this.onGlobalEvent.bind(this))}setListener(e,t,n,r){const o=this.getEventHandlerInfosForElement(e,!0),a=o.getHandler(t);if(a)this.eventInfoStore.update(a.eventHandlerId,n);else{const a={element:e,eventName:t,eventHandlerId:n,renderingComponentId:r};this.eventInfoStore.add(a),o.setHandler(t,a)}}getHandler(e){return this.eventInfoStore.get(e)}removeListener(e){const t=this.eventInfoStore.remove(e);if(t){const e=t.element,n=this.getEventHandlerInfosForElement(e,!1);n&&n.removeHandler(t.eventName)}}notifyAfterClick(e){this.afterClickCallbacks.push(e),this.eventInfoStore.addGlobalListener("click")}setStopPropagation(e,t,n){this.getEventHandlerInfosForElement(e,!0).stopPropagation(t,n)}setPreventDefault(e,t,n){this.getEventHandlerInfosForElement(e,!0).preventDefault(t,n)}onGlobalEvent(e){if(!(e.target instanceof Element))return;this.dispatchGlobalEventToAllElements(e.type,e);const t=(n=e.type,i.get(n));var n;t&&t.forEach((t=>this.dispatchGlobalEventToAllElements(t,e))),"click"===e.type&&this.afterClickCallbacks.forEach((t=>t(e)))}dispatchGlobalEventToAllElements(e,t){const n=t.composedPath();let r=n.shift(),a=null,i=!1;const s=Object.prototype.hasOwnProperty.call(k,e);let l=!1;for(;r;){const h=r,f=this.getEventHandlerInfosForElement(h,!1);if(f){const n=f.getHandler(e);if(n&&(u=h,d=t.type,!((u instanceof HTMLButtonElement||u instanceof HTMLInputElement||u instanceof HTMLTextAreaElement||u instanceof HTMLSelectElement)&&Object.prototype.hasOwnProperty.call(T,d)&&u.disabled))){if(!i){const n=c(e);a=(null==n?void 0:n.createEventArgs)?n.createEventArgs(t):{},i=!0}Object.prototype.hasOwnProperty.call(R,t.type)&&t.preventDefault(),D(this.browserRendererId,{eventHandlerId:n.eventHandlerId,eventName:e,eventFieldInfo:o.fromEvent(n.renderingComponentId,t)},a)}f.stopPropagation(e)&&(l=!0),f.preventDefault(e)&&t.preventDefault()}r=s||l?void 0:n.shift()}var u,d}getEventHandlerInfosForElement(e,t){return Object.prototype.hasOwnProperty.call(e,this.eventsCollectionKey)?e[this.eventsCollectionKey]:t?e[this.eventsCollectionKey]=new L:null}}_.nextEventDelegatorId=0;class O{constructor(e){this.globalListener=e,this.infosByEventHandlerId={},this.countByEventName={},s.push(this.handleEventNameAliasAdded.bind(this))}add(e){if(this.infosByEventHandlerId[e.eventHandlerId])throw new Error(`Event ${e.eventHandlerId} is already tracked`);this.infosByEventHandlerId[e.eventHandlerId]=e,this.addGlobalListener(e.eventName)}get(e){return this.infosByEventHandlerId[e]}addGlobalListener(e){if(e=l(e),Object.prototype.hasOwnProperty.call(this.countByEventName,e))this.countByEventName[e]++;else{this.countByEventName[e]=1;const t=Object.prototype.hasOwnProperty.call(k,e);document.addEventListener(e,this.globalListener,t)}}update(e,t){if(Object.prototype.hasOwnProperty.call(this.infosByEventHandlerId,t))throw new Error(`Event ${t} is already tracked`);const n=this.infosByEventHandlerId[e];delete this.infosByEventHandlerId[e],n.eventHandlerId=t,this.infosByEventHandlerId[t]=n}remove(e){const t=this.infosByEventHandlerId[e];if(t){delete this.infosByEventHandlerId[e];const n=l(t.eventName);0==--this.countByEventName[n]&&(delete this.countByEventName[n],document.removeEventListener(n,this.globalListener))}return t}handleEventNameAliasAdded(e,t){if(Object.prototype.hasOwnProperty.call(this.countByEventName,e)){const n=this.countByEventName[e];delete this.countByEventName[e],document.removeEventListener(e,this.globalListener),this.addGlobalListener(t),this.countByEventName[t]+=n-1}}}class L{constructor(){this.handlers={},this.preventDefaultFlags=null,this.stopPropagationFlags=null}getHandler(e){return Object.prototype.hasOwnProperty.call(this.handlers,e)?this.handlers[e]:null}setHandler(e,t){this.handlers[e]=t}removeHandler(e){delete this.handlers[e]}preventDefault(e,t){return void 0!==t&&(this.preventDefaultFlags=this.preventDefaultFlags||{},this.preventDefaultFlags[e]=t),!!this.preventDefaultFlags&&this.preventDefaultFlags[e]}stopPropagation(e,t){return void 0!==t&&(this.stopPropagationFlags=this.stopPropagationFlags||{},this.stopPropagationFlags[e]=t),!!this.stopPropagationFlags&&this.stopPropagationFlags[e]}}function x(e){const t={};return e.forEach((e=>{t[e]=!0})),t}const F=Symbol(),M=Symbol();function P(e,t){if(F in e)return e;const n=[];if(e.childNodes.length>0){if(!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");e.childNodes.forEach((t=>{const r=P(t,!0);r[M]=e,n.push(r)}))}return e[F]=n,e}function B(e){const t=W(e);for(;t.length;)J(e,0)}function j(e,t){const n=document.createComment("!");return H(n,e,t),n}function H(e,t,n){const r=e;let o=e;if(F in e){const t=G(r);if(t!==e){const n=new Range;n.setStartBefore(e),n.setEndAfter(t),o=n.extractContents()}}const a=U(r);if(a){const e=W(a),t=Array.prototype.indexOf.call(e,r);e.splice(t,1),delete r[M]}const i=W(t);if(n0;)J(n,0)}const r=n;r.parentNode.removeChild(r)}function U(e){return e[M]||null}function z(e,t){return W(e)[t]}function $(e){const t=X(e);return"http://www.w3.org/2000/svg"===t.namespaceURI&&"foreignObject"!==t.tagName}function W(e){return e[F]}function K(e){const t=W(U(e));return t[Array.prototype.indexOf.call(t,e)+1]||null}function V(e,t){const n=W(e);t.forEach((e=>{e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=G(e.moveRangeStart)})),t.forEach((t=>{const r=document.createComment("marker");t.moveToBeforeMarker=r;const o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):Y(r,e)})),t.forEach((e=>{const t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd;let a=r;for(;a;){const e=a.nextSibling;if(n.insertBefore(a,t),a===o)break;a=e}n.removeChild(t)})),t.forEach((e=>{n[e.toSiblingIndex]=e.moveRangeStart}))}function X(e){if(e instanceof Element||e instanceof DocumentFragment)return e;if(e instanceof Comment)return e.parentNode;throw new Error("Not a valid logical element")}function Y(e,t){if(t instanceof Element||t instanceof DocumentFragment)t.appendChild(e);else{if(!(t instanceof Comment))throw new Error(`Cannot append node because the parent is not a valid logical element. Parent: ${t}`);{const n=K(t);n?n.parentNode.insertBefore(e,n):Y(e,U(t))}}}function G(e){if(e instanceof Element||e instanceof DocumentFragment)return e;const t=K(e);if(t)return t.previousSibling;{const t=U(e);return t instanceof Element||t instanceof DocumentFragment?t.lastChild:G(t)}}function q(e){return`_bl_${e}`}Symbol();const Z="__internalId";e.attachReviver(((e,t)=>t&&"object"==typeof t&&Object.prototype.hasOwnProperty.call(t,Z)&&"string"==typeof t[Z]?function(e){const t=`[${q(e)}]`;return document.querySelector(t)}(t[Z]):t));const Q="_blazorDeferredValue";function ee(e){return"select-multiple"===e.type}function te(e,t){e.value=t||""}function ne(e,t){e instanceof HTMLSelectElement?ee(e)?function(e,t){t||(t=[]);for(let n=0;n{Ce()&&function(e,t){if(0!==e.button||function(e){return e.ctrlKey||e.shiftKey||e.altKey||e.metaKey}(e))return;if(e.defaultPrevented)return;const n=function(e){const t=!window._blazorDisableComposedPath&&e.composedPath&&e.composedPath();if(t){for(let e=0;e{const t=document.createElement("script");t.textContent=e.textContent,e.getAttributeNames().forEach((n=>{t.setAttribute(n,e.getAttribute(n))})),e.parentNode.replaceChild(t,e)})),oe.content));var i;let s=0;for(;a.firstChild;)H(a.firstChild,o,s++)}applyAttribute(e,t,n,r){const o=e.frameReader,a=o.attributeName(r),i=o.attributeEventHandlerId(r);if(i){const e=he(a);return void this.eventDelegator.setListener(n,e,i,t)}const s=o.attributeValue(r);this.setOrRemoveAttributeOrProperty(n,a,s)}insertFrameRange(e,t,n,r,o,a,i){const s=r;for(let s=a;s{He(t,e)})},enableNavigationInterception:function(e){if(void 0!==me&&me!==e)throw new Error("Only one interactive runtime may enable navigation interception at a time.");me=e},setHasLocationChangingListeners:function(e,t){const n=Re.get(e);if(!n)throw new Error(`Renderer with ID '${e}' is not listening for navigation events`);n.hasLocationChangingEventListeners=t},endLocationChanging:function(e,t){_e&&e===ke&&(_e(t),_e=null)},navigateTo:function(e,t){xe(e,t,!0)},refresh:function(e){!e&&we()?Ee(location.href,!0):location.reload()},getBaseURI:()=>document.baseURI,getLocationHref:()=>location.href,scrollToElement:Le};function Le(e){const t=document.getElementById(e);return!!t&&(t.scrollIntoView(),!0)}function xe(e,t,n=!1){const r=Se(e);!t.forceLoad&&ye(r)?ze()?Fe(r,!1,t.replaceHistoryEntry,t.historyEntryState,n):Ee(r,t.replaceHistoryEntry):function(e,t){if(location.href===e){const t=e+"?";history.replaceState(null,"",t),location.replace(e)}else t?location.replace(e):location.href=e}(e,t.replaceHistoryEntry)}async function Fe(e,t,n,r=void 0,o=!1){if(Be(),function(e){const t=e.indexOf("#");return t>-1&&location.href.replace(location.hash,"")===e.substring(0,t)}(e))return void function(e,t,n){Me(e,t,n);const r=e.indexOf("#");r!==e.length-1&&Le(e.substring(r+1))}(e,n,r);const a=Ue();(o||!(null==a?void 0:a.hasLocationChangingEventListeners)||await je(e,r,t,a))&&(ge=!0,Me(e,n,r),await He(t))}function Me(e,t,n=void 0){t?history.replaceState({userState:n,_index:Ne},"",e):(Ne++,history.pushState({userState:n,_index:Ne},"",e))}function Pe(e){return new Promise((t=>{const n=Te;Te=()=>{Te=n,t()},history.go(e)}))}function Be(){_e&&(_e(!1),_e=null)}function je(e,t,n,r){return new Promise((o=>{Be(),ke++,_e=o,r.locationChanging(ke,e,t,n)}))}async function He(e,t){const n=null!=t?t:location.href;await Promise.all(Array.from(Re,(async([t,r])=>{var o,a;a=t,S.has(a)&&await r.locationChanged(n,null===(o=history.state)||void 0===o?void 0:o.userState,e)})))}async function Je(e){var t,n;Te&&ze()&&await Te(e),Ne=null!==(n=null===(t=history.state)||void 0===t?void 0:t._index)&&void 0!==n?n:0}function Ue(){const e=De();if(void 0!==e)return Re.get(e)}function ze(){return Ce()||!we()}const $e={focus:function(e,t){if(e instanceof HTMLElement)e.focus({preventScroll:t});else{if(!(e instanceof SVGElement))throw new Error("Unable to focus an invalid element.");if(!e.hasAttribute("tabindex"))throw new Error("Unable to focus an SVG element that does not have a tabindex.");e.focus({preventScroll:t})}},focusBySelector:function(e,t){const n=document.querySelector(e);n&&(n.hasAttribute("tabindex")||(n.tabIndex=-1),n.focus({preventScroll:!0}))}},We={init:function(e,t,n,r=50){const o=Ve(t);(o||document.documentElement).style.overflowAnchor="none";const a=document.createRange();h(n.parentElement)&&(t.style.display="table-row",n.style.display="table-row");const i=new IntersectionObserver((function(r){r.forEach((r=>{var o;if(!r.isIntersecting)return;a.setStartAfter(t),a.setEndBefore(n);const i=a.getBoundingClientRect().height,s=null===(o=r.rootBounds)||void 0===o?void 0:o.height;r.target===t?e.invokeMethodAsync("OnSpacerBeforeVisible",r.intersectionRect.top-r.boundingClientRect.top,i,s):r.target===n&&n.offsetHeight>0&&e.invokeMethodAsync("OnSpacerAfterVisible",r.boundingClientRect.bottom-r.intersectionRect.bottom,i,s)}))}),{root:o,rootMargin:`${r}px`});i.observe(t),i.observe(n);const s=d(t),c=d(n),{observersByDotNetObjectId:l,id:u}=Xe(e);function d(e){const t={attributes:!0},n=new MutationObserver(((n,r)=>{h(e.parentElement)&&(r.disconnect(),e.style.display="table-row",r.observe(e,t)),i.unobserve(e),i.observe(e)}));return n.observe(e,t),n}function h(e){return null!==e&&(e instanceof HTMLTableElement&&""===e.style.display||"table"===e.style.display||e instanceof HTMLTableSectionElement&&""===e.style.display||"table-row-group"===e.style.display)}l[u]={intersectionObserver:i,mutationObserverBefore:s,mutationObserverAfter:c}},dispose:function(e){const{observersByDotNetObjectId:t,id:n}=Xe(e),r=t[n];r&&(r.intersectionObserver.disconnect(),r.mutationObserverBefore.disconnect(),r.mutationObserverAfter.disconnect(),e.dispose(),delete t[n])}},Ke=Symbol();function Ve(e){return e&&e!==document.body&&e!==document.documentElement?"visible"!==getComputedStyle(e).overflowY?e:Ve(e.parentElement):null}function Xe(e){var t;const n=e._callDispatcher,r=e._id;return null!==(t=n[Ke])&&void 0!==t||(n[Ke]={}),{observersByDotNetObjectId:n[Ke],id:r}}const Ye={getAndRemoveExistingTitle:function(){var e;const t=document.head?document.head.getElementsByTagName("title"):[];if(0===t.length)return null;let n=null;for(let r=t.length-1;r>=0;r--){const o=t[r],a=o.previousSibling;a instanceof Comment&&null!==U(a)||(null===n&&(n=o.textContent),null===(e=o.parentNode)||void 0===e||e.removeChild(o))}return n}},Ge={init:function(e,t){t._blazorInputFileNextFileId=0,t.addEventListener("click",(function(){t.value=""})),t.addEventListener("change",(function(){t._blazorFilesById={};const n=Array.prototype.map.call(t.files,(function(e){const n={id:++t._blazorInputFileNextFileId,lastModified:new Date(e.lastModified).toISOString(),name:e.name,size:e.size,contentType:e.type,readPromise:void 0,arrayBuffer:void 0,blob:e};return t._blazorFilesById[n.id]=n,n}));e.invokeMethodAsync("NotifyChange",n)}))},toImageFile:async function(e,t,n,r,o){const a=qe(e,t),i=await new Promise((function(e){const t=new Image;t.onload=function(){URL.revokeObjectURL(t.src),e(t)},t.onerror=function(){t.onerror=null,URL.revokeObjectURL(t.src)},t.src=URL.createObjectURL(a.blob)})),s=await new Promise((function(e){var t;const a=Math.min(1,r/i.width),s=Math.min(1,o/i.height),c=Math.min(a,s),l=document.createElement("canvas");l.width=Math.round(i.width*c),l.height=Math.round(i.height*c),null===(t=l.getContext("2d"))||void 0===t||t.drawImage(i,0,0,l.width,l.height),l.toBlob(e,n)})),c={id:++e._blazorInputFileNextFileId,lastModified:a.lastModified,name:a.name,size:(null==s?void 0:s.size)||0,contentType:n,blob:s||a.blob};return e._blazorFilesById[c.id]=c,c},readFileData:async function(e,t){return qe(e,t).blob}};function qe(e,t){const n=e._blazorFilesById[t];if(!n)throw new Error(`There is no file with ID ${t}. The file list may have changed. See https://aka.ms/aspnet/blazor-input-file-multiple-selections.`);return n}const Ze=new Set,Qe={enableNavigationPrompt:function(e){0===Ze.size&&window.addEventListener("beforeunload",et),Ze.add(e)},disableNavigationPrompt:function(e){Ze.delete(e),0===Ze.size&&window.removeEventListener("beforeunload",et)}};function et(e){e.preventDefault(),e.returnValue=!0}const tt=new Map,nt={navigateTo:function(e,t,n=!1){xe(e,t instanceof Object?t:{forceLoad:t,replaceHistoryEntry:n})},registerCustomEventType:function(e,t){if(!t)throw new Error("The options parameter is required.");if(a.has(e))throw new Error(`The event '${e}' is already registered.`);if(t.browserEventName){const n=i.get(t.browserEventName);n?n.push(e):i.set(t.browserEventName,[e]),s.forEach((n=>n(e,t.browserEventName)))}a.set(e,t)},rootComponents:g,runtime:{},_internal:{navigationManager:Oe,domWrapper:$e,Virtualize:We,PageTitle:Ye,InputFile:Ge,NavigationLock:Qe,getJSDataStreamChunk:async function(e,t,n){return e instanceof Blob?await async function(e,t,n){const r=e.slice(t,t+n),o=await r.arrayBuffer();return new Uint8Array(o)}(e,t,n):function(e,t,n){return new Uint8Array(e.buffer,e.byteOffset+t,n)}(e,t,n)},attachWebRendererInterop:function(t,n,r,o){var a,i;if(S.has(t))throw new Error(`Interop methods are already registered for renderer ${t}`);S.set(t,n),r&&o&&Object.keys(r).length>0&&function(t,n,r){if(p)throw new Error("Dynamic root components have already been enabled.");p=t,b=n;for(const[t,o]of Object.entries(r)){const r=e.findJSFunction(t,0);for(const e of o)r(e,n[e])}}(A(t),r,o),null===(i=null===(a=C.get(t))||void 0===a?void 0:a[0])||void 0===i||i.call(a),function(e){for(const t of I)t(e)}(t)}}};window.Blazor=nt;let rt=!1;const ot="function"==typeof TextDecoder?new TextDecoder("utf-8"):null,at=ot?ot.decode.bind(ot):function(e){let t=0;const n=e.length,r=[],o=[];for(;t65535&&(o-=65536,r.push(o>>>10&1023|55296),o=56320|1023&o),r.push(o)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")},it=Math.pow(2,32),st=Math.pow(2,21)-1;function ct(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24}function lt(e,t){return e[t]+(e[t+1]<<8)+(e[t+2]<<16)+(e[t+3]<<24>>>0)}function ut(e,t){const n=lt(e,t+4);if(n>st)throw new Error(`Cannot read uint64 with high order part ${n}, because the result would exceed Number.MAX_SAFE_INTEGER.`);return n*it+lt(e,t)}class dt{constructor(e){this.batchData=e;const t=new pt(e);this.arrayRangeReader=new bt(e),this.arrayBuilderSegmentReader=new vt(e),this.diffReader=new ht(e),this.editReader=new ft(e,t),this.frameReader=new mt(e,t)}updatedComponents(){return ct(this.batchData,this.batchData.length-20)}referenceFrames(){return ct(this.batchData,this.batchData.length-16)}disposedComponentIds(){return ct(this.batchData,this.batchData.length-12)}disposedEventHandlerIds(){return ct(this.batchData,this.batchData.length-8)}updatedComponentsEntry(e,t){const n=e+4*t;return ct(this.batchData,n)}referenceFramesEntry(e,t){return e+20*t}disposedComponentIdsEntry(e,t){const n=e+4*t;return ct(this.batchData,n)}disposedEventHandlerIdsEntry(e,t){const n=e+8*t;return ut(this.batchData,n)}}class ht{constructor(e){this.batchDataUint8=e}componentId(e){return ct(this.batchDataUint8,e)}edits(e){return e+4}editsEntry(e,t){return e+16*t}}class ft{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}editType(e){return ct(this.batchDataUint8,e)}siblingIndex(e){return ct(this.batchDataUint8,e+4)}newTreeIndex(e){return ct(this.batchDataUint8,e+8)}moveToSiblingIndex(e){return ct(this.batchDataUint8,e+8)}removedAttributeName(e){const t=ct(this.batchDataUint8,e+12);return this.stringReader.readString(t)}}class mt{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}frameType(e){return ct(this.batchDataUint8,e)}subtreeLength(e){return ct(this.batchDataUint8,e+4)}elementReferenceCaptureId(e){const t=ct(this.batchDataUint8,e+4);return this.stringReader.readString(t)}componentId(e){return ct(this.batchDataUint8,e+8)}elementName(e){const t=ct(this.batchDataUint8,e+8);return this.stringReader.readString(t)}textContent(e){const t=ct(this.batchDataUint8,e+4);return this.stringReader.readString(t)}markupContent(e){const t=ct(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeName(e){const t=ct(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeValue(e){const t=ct(this.batchDataUint8,e+8);return this.stringReader.readString(t)}attributeEventHandlerId(e){return ut(this.batchDataUint8,e+12)}}class pt{constructor(e){this.batchDataUint8=e,this.stringTableStartIndex=ct(e,e.length-4)}readString(e){if(-1===e)return null;{const n=ct(this.batchDataUint8,this.stringTableStartIndex+4*e),r=function(e,t){let n=0,r=0;for(let o=0;o<4;o++){const a=e[t+o];if(n|=(127&a)<async function(e,n){const r=function(e){const t=document.baseURI;return t.endsWith("/")?`${t}${e}`:`${t}/${e}`}(n),o=await import(r);if(void 0!==o){if(e.singleRuntime){const{beforeStart:n,afterStarted:r,beforeWebAssemblyStart:i,afterWebAssemblyStarted:s,beforeServerStart:c,afterServerStarted:l}=o;let u=n;e.webRendererId===Nt.Server&&c&&(u=c),e.webRendererId===Nt.WebAssembly&&i&&(u=i);let d=r;return e.webRendererId===Nt.Server&&l&&(d=l),e.webRendererId===Nt.WebAssembly&&s&&(d=s),a(e,u,d,t)}return function(e,t,n){var o;const i=n[0],{beforeStart:s,afterStarted:c,beforeWebStart:l,afterWebStarted:u,beforeWebAssemblyStart:d,afterWebAssemblyStarted:h,beforeServerStart:f,afterServerStarted:m}=t,p=!(l||u||d||h||f||m||!s&&!c),b=p&&i.enableClassicInitializers;if(p&&!i.enableClassicInitializers)null===(o=e.logger)||void 0===o||o.log(kt.Warning,`Initializer '${r}' will be ignored because multiple runtimes are available. use 'before(web|webAssembly|server)Start' and 'after(web|webAssembly|server)Started?' instead.)`);else if(b)return a(e,s,c,n);if(function(e){e.webAssembly?e.webAssembly.initializers||(e.webAssembly.initializers={beforeStart:[],afterStarted:[]}):e.webAssembly={initializers:{beforeStart:[],afterStarted:[]}},e.circuit?e.circuit.initializers||(e.circuit.initializers={beforeStart:[],afterStarted:[]}):e.circuit={initializers:{beforeStart:[],afterStarted:[]}}}(i),d&&i.webAssembly.initializers.beforeStart.push(d),h&&i.webAssembly.initializers.afterStarted.push(h),f&&i.circuit.initializers.beforeStart.push(f),m&&i.circuit.initializers.afterStarted.push(m),u&&e.afterStartedCallbacks.push(u),l)return l(i)}(e,o,t)}function a(e,t,n,r){if(n&&e.afterStartedCallbacks.push(n),t)return t(...r)}}(this,e))))}async invokeAfterStartedCallbacks(e){const t=(n=this.webRendererId,null===(r=C.get(n))||void 0===r?void 0:r[1]);var n,r;t&&await t,await Promise.all(this.afterStartedCallbacks.map((t=>t(e))))}}let Ot,Lt=!1;async function xt(){if(Lt)throw new Error("Blazor has already started.");Lt=!0,Ot=e.attachDispatcher({beginInvokeDotNetFromJS:Et,endInvokeJSFromDotNet:St,sendByteArray:It});const t=await async function(){const e=await fetch("_framework/blazor.modules.json",{method:"GET",credentials:"include",cache:"no-cache"}),t=await e.json(),n=new _t;return await n.importInitializersAsync(t,[]),n}();(function(){const e={AttachToDocument:(e,t)=>{!function(e,t,n){const r="::before";let o=!1;if(e.endsWith("::after"))e=e.slice(0,-7),o=!0;else if(e.endsWith(r))throw new Error(`The '${r}' selector is not supported.`);const a=function(e){const t=m.get(e);if(t)return m.delete(e),t}(e)||document.querySelector(e);if(!a)throw new Error(`Could not find any element matching selector '${e}'.`);!function(e,t,n,r){let o=fe[e];o||(o=new le(e),fe[e]=o),o.attachRootComponentToLogicalElement(n,t,r)}(n,P(a,!0),t,o)}(t,e,Nt.WebView)},RenderBatch:(e,t)=>{try{const n=Tt(t);(function(e,t){const n=fe[e];if(!n)throw new Error(`There is no browser renderer with ID ${e}.`);const r=t.arrayRangeReader,o=t.updatedComponents(),a=r.values(o),i=r.count(o),s=t.referenceFrames(),c=r.values(s),l=t.diffReader;for(let e=0;e{yt=!0,console.error(`${e}\n${t}`),function(){const e=document.querySelector("#blazor-error-ui");e&&(e.style.display="block"),rt||(rt=!0,document.querySelectorAll("#blazor-error-ui .reload").forEach((e=>{e.onclick=function(e){location.reload(),e.preventDefault()}})),document.querySelectorAll("#blazor-error-ui .dismiss").forEach((e=>{e.onclick=function(e){const t=document.querySelector("#blazor-error-ui");t&&(t.style.display="none"),e.preventDefault()}})))}()},BeginInvokeJS:Ot.beginInvokeJSFromDotNet.bind(Ot),EndInvokeDotNet:Ot.endInvokeDotNetFromJS.bind(Ot),SendByteArrayToJS:Rt,Navigate:Oe.navigateTo,Refresh:Oe.refresh,SetHasLocationChangingListeners:e=>{Oe.setHasLocationChangingListeners(Nt.WebView,e)},EndLocationChanging:Oe.endLocationChanging};window.external.receiveMessage((t=>{const n=function(e){if(yt||!e||!e.startsWith(gt))return null;const t=e.substring(gt.length),[n,...r]=JSON.parse(t);return{messageType:n,args:r}}(t);if(n){if(!Object.prototype.hasOwnProperty.call(e,n.messageType))throw new Error(`Unsupported IPC message type '${n.messageType}'`);e[n.messageType].apply(null,n.args)}}))})(),nt._internal.receiveWebViewDotNetDataStream=Ft,Oe.enableNavigationInterception(Nt.WebView),Oe.listenForNavigationEvents(Nt.WebView,Ct,Dt),At("AttachPage",Oe.getBaseURI(),Oe.getLocationHref()),await t.invokeAfterStartedCallbacks(nt)}function Ft(e,t,n,r){!function(e,t,n,r,o){let a=tt.get(t);if(!a){const n=new ReadableStream({start(e){tt.set(t,e),a=e}});e.supplyDotNetStream(t,n)}o?(a.error(o),tt.delete(t)):0===r?(a.close(),tt.delete(t)):a.enqueue(n.length===r?n:n.subarray(0,r))}(Ot,e,t,n,r)}nt.start=xt,window.DotNet=e,document&&document.currentScript&&"false"!==document.currentScript.getAttribute("autostart")&&xt()})(); \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Boot.Server.Common.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Boot.Server.Common.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Boot.Server.Common.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Boot.Server.Common.ts 2023-11-13 13:20:34.000000000 +0000 @@ -10,27 +10,47 @@ import { discoverServerPersistedState, ServerComponentDescriptor } from './Services/ComponentDescriptorDiscovery'; import { fetchAndInvokeInitializers } from './JSInitializers/JSInitializers.Server'; import { RootComponentManager } from './Services/RootComponentManager'; +import { WebRendererId } from './Rendering/WebRendererId'; -let started = false; +let initializersPromise: Promise | undefined; let appState: string; let circuit: CircuitManager; let options: CircuitStartOptions; let logger: ConsoleLogger; +let serverStartPromise: Promise; +let circuitStarting: Promise | undefined; -export function setCircuitOptions(circuitUserOptions?: Partial) { +export function setCircuitOptions(initializersReady: Promise>) { if (options) { throw new Error('Circuit options have already been configured.'); } - options = resolveOptions(circuitUserOptions); + if (options) { + throw new Error('WebAssembly options have already been configured.'); + } + + initializersPromise = setOptions(initializersReady); + + async function setOptions(initializers: Promise>): Promise { + const configuredOptions = await initializers; + options = resolveOptions(configuredOptions); + } } -export async function startServer(components: RootComponentManager): Promise { - if (started) { +export function startServer(components: RootComponentManager): Promise { + if (serverStartPromise !== undefined) { throw new Error('Blazor Server has already started.'); } - started = true; + serverStartPromise = new Promise(startServerCore.bind(null, components)); + + return serverStartPromise; +} + +async function startServerCore(components: RootComponentManager, resolve: () => void, _: any) { + await initializersPromise; + const jsInitializer = await fetchAndInvokeInitializers(options); + appState = discoverServerPersistedState(document) || ''; logger = new ConsoleLogger(options.logLevel); circuit = new CircuitManager(components, appState, options, logger); @@ -55,7 +75,7 @@ options.reconnectionHandler = options.reconnectionHandler || Blazor.defaultReconnectionHandler; // Configure navigation via SignalR - Blazor._internal.navigationManager.listenForNavigationEvents((uri: string, state: string | undefined, intercepted: boolean): Promise => { + Blazor._internal.navigationManager.listenForNavigationEvents(WebRendererId.Server, (uri: string, state: string | undefined, intercepted: boolean): Promise => { return circuit.sendLocationChanged(uri, state, intercepted); }, (callId: number, uri: string, state: string | undefined, intercepted: boolean): Promise => { return circuit.sendLocationChanging(callId, uri, state, intercepted); @@ -64,11 +84,10 @@ Blazor._internal.forceCloseConnection = () => circuit.disconnect(); Blazor._internal.sendJSDataStream = (data: ArrayBufferView | Blob, streamId: number, chunkSize: number) => circuit.sendJsDataStream(data, streamId, chunkSize); - const jsInitializer = await fetchAndInvokeInitializers(options); - const circuitStarted = await circuit.start(); if (!circuitStarted) { logger.log(LogLevel.Error, 'Failed to start the circuit.'); + resolve(); return; } @@ -83,20 +102,33 @@ logger.log(LogLevel.Information, 'Blazor server-side application started.'); jsInitializer.invokeAfterStartedCallbacks(Blazor); + resolve(); } -export function startCircuit(): Promise { - if (!started) { +export async function startCircuit(): Promise { + if (!serverStartPromise) { throw new Error('Cannot start the circuit until Blazor Server has started.'); } - if (circuit.didRenderingFail()) { + if (circuit && !circuit.isDisposedOrDisposing()) { + return true; + } + + // We might be starting a circuit already, if that's the case, we can just wait for that + // to finish. + if (circuitStarting) { + return await circuitStarting; + } + + await serverStartPromise; + + if (circuit && circuit.didRenderingFail()) { // We can't start a new circuit after a rendering failure because the renderer // might be in an invalid state. - return Promise.resolve(false); + return false; } - if (circuit.isDisposedOrDisposing()) { + if (circuit && circuit.isDisposedOrDisposing()) { // If the current circuit is no longer available, create a new one. appState = discoverServerPersistedState(document) || ''; circuit = new CircuitManager(circuit.getRootComponentManager(), appState, options, logger); @@ -104,11 +136,22 @@ // Start the circuit. If the circuit has already started, this will return the existing // circuit start promise. - return circuit.start(); + circuitStarting = circuit.start(); + // Once we are done, we need to clear the circuitStartingPromise to ensure that if the circuit gets + // disposed later on, we are able to start a new one. + clearCircuitStarting(circuitStarting); + return circuitStarting; +} + +async function clearCircuitStarting(circuitStartingPromise: Promise) { + await circuitStartingPromise; + if (circuitStarting === circuitStartingPromise) { + circuitStarting = undefined; + } } export function hasStartedServer(): boolean { - return started; + return serverStartPromise !== undefined; } export async function disposeCircuit() { @@ -116,9 +159,19 @@ } export function isCircuitAvailable(): boolean { - return !circuit.isDisposedOrDisposing(); + return circuit && !circuit.isDisposedOrDisposing(); } -export function updateServerRootComponents(operations: string): Promise|undefined { - return circuit.updateRootComponents(operations); +export function updateServerRootComponents(operations: string): Promise | undefined { + if (circuit && !circuit.isDisposedOrDisposing()) { + return circuit.updateRootComponents(operations); + } else { + scheduleWhenReady(operations); + } +} +async function scheduleWhenReady(operations: string) { + await serverStartPromise; + if (await startCircuit()) { + return circuit.updateRootComponents(operations); + } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Boot.Server.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Boot.Server.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Boot.Server.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Boot.Server.ts 2023-11-13 13:20:34.000000000 +0000 @@ -3,7 +3,7 @@ import { Blazor } from './GlobalExports'; import { shouldAutoStart } from './BootCommon'; -import { CircuitStartOptions } from './Platform/Circuits/CircuitStartOptions'; +import { CircuitStartOptions, resolveOptions } from './Platform/Circuits/CircuitStartOptions'; import { setCircuitOptions, startServer } from './Boot.Server.Common'; import { ServerComponentDescriptor, discoverComponents } from './Services/ComponentDescriptorDiscovery'; import { DotNet } from '@microsoft/dotnet-js-interop'; @@ -18,7 +18,8 @@ } started = true; - setCircuitOptions(userOptions); + const configuredOptions = resolveOptions(userOptions); + setCircuitOptions(Promise.resolve(configuredOptions || {})); JSEventRegistry.create(Blazor); const serverComponents = discoverComponents(document, 'server') as ServerComponentDescriptor[]; diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts 2023-11-13 13:20:34.000000000 +0000 @@ -5,7 +5,7 @@ import { DotNet } from '@microsoft/dotnet-js-interop'; import { Blazor } from './GlobalExports'; import * as Environment from './Environment'; -import { BINDING, monoPlatform, dispatcher } from './Platform/Mono/MonoPlatform'; +import { BINDING, monoPlatform, dispatcher, getInitializer } from './Platform/Mono/MonoPlatform'; import { renderBatch, getRendererer, attachRootComponentToElement, attachRootComponentToLogicalElement } from './Rendering/Renderer'; import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch'; import { PlatformApi, Pointer } from './Platform/Platform'; @@ -16,13 +16,16 @@ import { WebAssemblyComponentAttacher } from './Platform/WebAssemblyComponentAttacher'; import { MonoConfig } from 'dotnet'; import { RootComponentManager } from './Services/RootComponentManager'; +import { WebRendererId } from './Rendering/WebRendererId'; let options: Partial | undefined; +let initializersPromise: Promise; let platformLoadPromise: Promise | undefined; let loadedWebAssemblyPlatform = false; let started = false; let firstUpdate = true; let waitForRootComponents = false; +let startPromise: Promise | undefined; let resolveBootConfigPromise: (value: MonoConfig) => void; const bootConfigPromise = new Promise(resolve => { @@ -39,6 +42,11 @@ firstUpdate = false; } +let resolveInitializersPromise: (value: void) => void; +initializersPromise = new Promise(resolve => { + resolveInitializersPromise = resolve; +}); + export function isFirstUpdate() { return firstUpdate; } @@ -47,21 +55,31 @@ waitForRootComponents = true; } -export function setWebAssemblyOptions(webAssemblyOptions?: Partial) { +export function setWebAssemblyOptions(initializersReady: Promise>) { if (options) { throw new Error('WebAssembly options have already been configured.'); } + setOptions(initializersReady); + - options = webAssemblyOptions; + async function setOptions(initializers: Promise>) { + const configuredOptions = await initializers; + options = configuredOptions; + resolveInitializersPromise(); + } } -export async function startWebAssembly(components: RootComponentManager): Promise { - if (started) { +export function startWebAssembly(components: RootComponentManager): Promise { + if (startPromise !== undefined) { throw new Error('Blazor WebAssembly has already started.'); } - started = true; + startPromise = new Promise(startCore.bind(null, components)); + return startPromise; +} + +async function startCore(components: RootComponentManager, resolve, _) { if (inAuthRedirectIframe()) { // eslint-disable-next-line @typescript-eslint/no-empty-function await new Promise(() => { }); // See inAuthRedirectIframe for explanation @@ -110,7 +128,7 @@ } }; - Blazor._internal.navigationManager.listenForNavigationEvents(async (uri: string, state: string | undefined, intercepted: boolean): Promise => { + Blazor._internal.navigationManager.listenForNavigationEvents(WebRendererId.WebAssembly, async (uri: string, state: string | undefined, intercepted: boolean): Promise => { await dispatcher.invokeDotNetStaticMethodAsync( 'Microsoft.AspNetCore.Components.WebAssembly', 'NotifyLocationChanged', @@ -148,8 +166,11 @@ Blazor._internal.updateRootComponents = (operations: string) => Blazor._internal.dotNetExports?.UpdateRootComponentsCore(operations); + Blazor._internal.endUpdateRootComponents = (batchId: number) => + components.onAfterUpdateRootComponents?.(batchId); + Blazor._internal.attachRootComponentToElement = (selector, componentId, rendererId: any) => { - const element = componentAttacher.resolveRegisteredElement(selector, componentId); + const element = componentAttacher.resolveRegisteredElement(selector); if (!element) { attachRootComponentToElement(selector, componentId, rendererId); } else { @@ -169,11 +190,14 @@ platform.callEntryPoint(); // At this point .NET has been initialized (and has yielded), we can't await the promise becasue it will // only end when the app finishes running - api.invokeLibraryInitializers('afterStarted', [Blazor]); + const initializer = getInitializer(); + initializer.invokeAfterStartedCallbacks(Blazor); + started = true; + resolve(); } export function hasStartedWebAssembly(): boolean { - return started; + return startPromise !== undefined; } export function waitForBootConfigLoaded(): Promise { @@ -182,6 +206,7 @@ export function loadWebAssemblyPlatformIfNotStarted(): Promise { platformLoadPromise ??= (async () => { + await initializersPromise; const finalOptions = options ?? {}; const existingConfig = options?.configureRuntime; finalOptions.configureRuntime = (config) => { @@ -244,12 +269,26 @@ } export function updateWebAssemblyRootComponents(operations: string): void { - if (!started) { + if (!startPromise) { throw new Error('Blazor WebAssembly has not started.'); } if (!Blazor._internal.updateRootComponents) { throw new Error('Blazor WebAssembly has not initialized.'); + } + + if (!started) { + scheduleAfterStarted(operations); + } else { + Blazor._internal.updateRootComponents(operations); + } +} + +async function scheduleAfterStarted(operations: string): Promise { + await startPromise; + + if (!Blazor._internal.updateRootComponents) { + throw new Error('Blazor WebAssembly has not initialized.'); } Blazor._internal.updateRootComponents(operations); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Boot.WebAssembly.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Boot.WebAssembly.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Boot.WebAssembly.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Boot.WebAssembly.ts 2023-11-13 13:20:34.000000000 +0000 @@ -20,7 +20,7 @@ } started = true; - setWebAssemblyOptions(options); + setWebAssemblyOptions(Promise.resolve(options || {})); JSEventRegistry.create(Blazor); const webAssemblyComponents = discoverComponents(document, 'webassembly') as WebAssemblyComponentDescriptor[]; diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Boot.Web.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Boot.Web.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Boot.Web.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Boot.Web.ts 2023-11-13 13:20:34.000000000 +0000 @@ -20,6 +20,11 @@ import { hasProgrammaticEnhancedNavigationHandler, performProgrammaticEnhancedNavigation } from './Services/NavigationUtils'; import { attachComponentDescriptorHandler, registerAllComponentDescriptors } from './Rendering/DomMerging/DomSync'; import { JSEventRegistry } from './Services/JSEventRegistry'; +import { fetchAndInvokeInitializers } from './JSInitializers/JSInitializers.Web'; +import { ConsoleLogger } from './Platform/Logging/Loggers'; +import { LogLevel } from './Platform/Logging/Logger'; +import { resolveOptions } from './Platform/Circuits/CircuitStartOptions'; +import { JSInitializer } from './JSInitializers/JSInitializers'; let started = false; let rootComponentManager: WebRootComponentManager; @@ -30,7 +35,8 @@ } started = true; - + options = options || {}; + options.logLevel ??= LogLevel.Error; Blazor._internal.loadWebAssemblyQuicklyTimeout = 3000; // Defined here to avoid inadvertently imported enhanced navigation @@ -41,9 +47,6 @@ } }; - setCircuitOptions(options?.circuit); - setWebAssemblyOptions(options?.webAssembly); - rootComponentManager = new WebRootComponentManager(options?.ssr?.circuitInactivityTimeoutMs ?? 2000); const jsEventRegistry = JSEventRegistry.create(Blazor); @@ -68,17 +71,40 @@ // If stream rendering is used, this helps to ensure that only the final set of interactive // components produced by the stream render actually get activated for interactivity. if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', onInitialDomContentLoaded); + document.addEventListener('DOMContentLoaded', onInitialDomContentLoaded.bind(null, options)); } else { - onInitialDomContentLoaded(); + onInitialDomContentLoaded(options); } return Promise.resolve(); } -function onInitialDomContentLoaded() { +function onInitialDomContentLoaded(options: Partial) { + + // Retrieve and start invoking the initializers. + // Blazor server options get defaults that are configured before we invoke the initializers + // so we do the same here. + const initialCircuitOptions = resolveOptions(options?.circuit || {}); + options.circuit = initialCircuitOptions; + const logger = new ConsoleLogger(initialCircuitOptions.logLevel); + const initializersPromise = fetchAndInvokeInitializers(options, logger); + setCircuitOptions(resolveConfiguredOptions(initializersPromise, initialCircuitOptions)); + setWebAssemblyOptions(resolveConfiguredOptions(initializersPromise, options?.webAssembly || {})); + registerAllComponentDescriptors(document); rootComponentManager.onDocumentUpdated(); + + callAfterStartedCallbacks(initializersPromise); +} + +async function resolveConfiguredOptions(initializers: Promise, options: TOptions): Promise { + await initializers; + return options; +} + +async function callAfterStartedCallbacks(initializersPromise: Promise): Promise { + const initializers = await initializersPromise; + await initializers.invokeAfterStartedCallbacks(Blazor); } Blazor.start = boot; diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Boot.WebView.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Boot.WebView.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Boot.WebView.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Boot.WebView.ts 2023-11-13 13:20:34.000000000 +0000 @@ -9,6 +9,7 @@ import { sendAttachPage, sendBeginInvokeDotNetFromJS, sendEndInvokeJSFromDotNet, sendByteArray, sendLocationChanged, sendLocationChanging } from './Platform/WebView/WebViewIpcSender'; import { fetchAndInvokeInitializers } from './JSInitializers/JSInitializers.WebView'; import { receiveDotNetDataStream } from './StreamingInterop'; +import { WebRendererId } from './Rendering/WebRendererId'; let started = false; @@ -32,8 +33,8 @@ Blazor._internal.receiveWebViewDotNetDataStream = receiveWebViewDotNetDataStream; - navigationManagerFunctions.enableNavigationInterception(); - navigationManagerFunctions.listenForNavigationEvents(sendLocationChanged, sendLocationChanging); + navigationManagerFunctions.enableNavigationInterception(WebRendererId.WebView); + navigationManagerFunctions.listenForNavigationEvents(WebRendererId.WebView, sendLocationChanged, sendLocationChanging); sendAttachPage(navigationManagerFunctions.getBaseURI(), navigationManagerFunctions.getLocationHref()); await jsInitializer.invokeAfterStartedCallbacks(Blazor); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/GlobalExports.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/GlobalExports.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/GlobalExports.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/GlobalExports.ts 2023-11-13 13:20:34.000000000 +0000 @@ -59,6 +59,7 @@ getPersistedState?: () => string; getInitialComponentsUpdate?: () => Promise; updateRootComponents?: (operations: string) => void; + endUpdateRootComponents?: (batchId: number) => void; attachRootComponentToElement?: (arg0: any, arg1: any, arg2: any, arg3: any) => void; registeredComponents?: { getRegisteredComponentsCount: () => number; diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.Server.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.Server.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.Server.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.Server.ts 2023-11-13 13:20:34.000000000 +0000 @@ -2,9 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. import { CircuitStartOptions } from '../Platform/Circuits/CircuitStartOptions'; +import { WebRendererId } from '../Rendering/WebRendererId'; import { JSInitializer } from './JSInitializers'; export async function fetchAndInvokeInitializers(options: Partial) : Promise { + if (options.initializers) { + // Initializers were already resolved, so we don't have to fetch them, we just invoke the beforeStart ones + // and return the list of afterStarted ones so they get processed in the same way as in traditional Blazor Server. + await Promise.all(options.initializers.beforeStart.map(i => i(options))); + return new JSInitializer(/* singleRuntime: */ false, undefined, options.initializers.afterStarted, WebRendererId.Server); + } + const jsInitializersResponse = await fetch('_blazor/initializers', { method: 'GET', credentials: 'include', @@ -12,7 +20,7 @@ }); const initializers: string[] = await jsInitializersResponse.json(); - const jsInitializer = new JSInitializer(); + const jsInitializer = new JSInitializer(/* singleRuntime: */ true, undefined, undefined, WebRendererId.Server); await jsInitializer.importInitializersAsync(initializers, [options]); return jsInitializer; } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.ts 2023-11-13 13:20:34.000000000 +0000 @@ -1,16 +1,43 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { Blazor } from '../GlobalExports'; -import { firstRendererAttached } from '../Rendering/WebRendererInteropMethods'; +import { Blazor, IBlazor } from '../GlobalExports'; +import { AfterBlazorServerStartedCallback, BeforeBlazorServerStartedCallback, CircuitStartOptions, ServerInitializers } from '../Platform/Circuits/CircuitStartOptions'; +import { LogLevel, Logger } from '../Platform/Logging/Logger'; +import { AfterBlazorWebAssemblyStartedCallback, BeforeBlazorWebAssemblyStartedCallback, WebAssemblyInitializers, WebAssemblyStartOptions } from '../Platform/WebAssemblyStartOptions'; +import { WebStartOptions } from '../Platform/WebStartOptions'; +import { WebRendererId } from '../Rendering/WebRendererId'; +import { getRendererAttachedPromise } from '../Rendering/WebRendererInteropMethods'; type BeforeBlazorStartedCallback = (...args: unknown[]) => Promise; export type AfterBlazorStartedCallback = (blazor: typeof Blazor) => Promise; -export type BlazorInitializer = { beforeStart: BeforeBlazorStartedCallback, afterStarted: AfterBlazorStartedCallback }; +type BeforeBlazorWebStartedCallback = (options: WebStartOptions) => Promise; +type AfterBlazorWebStartedCallback = (blazor: IBlazor) => Promise; +export type BlazorInitializer = { + beforeStart: BeforeBlazorStartedCallback, + afterStarted: AfterBlazorStartedCallback, + beforeWebStart: BeforeBlazorWebStartedCallback, + afterWebStarted: AfterBlazorWebStartedCallback, + beforeWebAssemblyStart: BeforeBlazorWebAssemblyStartedCallback, + afterWebAssemblyStarted: AfterBlazorWebAssemblyStartedCallback, + beforeServerStart: BeforeBlazorServerStartedCallback, + afterServerStarted: AfterBlazorServerStartedCallback, +}; export class JSInitializer { private afterStartedCallbacks: AfterBlazorStartedCallback[] = []; + constructor( + private singleRuntime = true, + private logger?: Logger, + afterstartedCallbacks?: AfterBlazorStartedCallback[], + private webRendererId: number = 0 + ) { + if (afterstartedCallbacks) { + this.afterStartedCallbacks.push(...afterstartedCallbacks); + } + } + async importInitializersAsync(initializerFiles: string[], initializerArguments: unknown[]): Promise { // This code is not called on WASM, because library intializers are imported by runtime. @@ -29,19 +56,112 @@ if (initializer === undefined) { return; } - const { beforeStart: beforeStart, afterStarted: afterStarted } = initializer; - if (afterStarted) { - jsInitializer.afterStartedCallbacks.push(afterStarted); + + if (!jsInitializer.singleRuntime) { + return runMultiRuntimeInitializers(jsInitializer, initializer, initializerArguments); + } else { + const { beforeStart, afterStarted, beforeWebAssemblyStart, afterWebAssemblyStarted, beforeServerStart, afterServerStarted } = initializer; + let finalBeforeStart = beforeStart; + if (jsInitializer.webRendererId === WebRendererId.Server && beforeServerStart) { + finalBeforeStart = beforeServerStart as unknown as BeforeBlazorStartedCallback; + } + if (jsInitializer.webRendererId === WebRendererId.WebAssembly && beforeWebAssemblyStart) { + finalBeforeStart = beforeWebAssemblyStart as unknown as BeforeBlazorStartedCallback; + } + let finalAfterStarted = afterStarted; + if (jsInitializer.webRendererId === WebRendererId.Server && afterServerStarted) { + finalAfterStarted = afterServerStarted; + } + if (jsInitializer.webRendererId === WebRendererId.WebAssembly && afterWebAssemblyStarted) { + finalAfterStarted = afterWebAssemblyStarted; + } + + return runClassicInitializers(jsInitializer, finalBeforeStart, finalAfterStarted, initializerArguments); + } + + function runMultiRuntimeInitializers( + jsInitializer: JSInitializer, + initializerModule: Partial, initializerArguments: unknown[]): void | PromiseLike { + const options = initializerArguments[0] as WebStartOptions; + const { beforeStart, afterStarted, beforeWebStart, afterWebStarted, beforeWebAssemblyStart, afterWebAssemblyStarted, beforeServerStart, afterServerStarted } = initializerModule; + const runtimeSpecificExports = !!(beforeWebStart || afterWebStarted || beforeWebAssemblyStart || afterWebAssemblyStarted || beforeServerStart || afterServerStarted); + const hasOnlyClassicInitializers = !!(!runtimeSpecificExports && (beforeStart || afterStarted)); + const runLegacyInitializers = hasOnlyClassicInitializers && options.enableClassicInitializers; + if (hasOnlyClassicInitializers && !options.enableClassicInitializers) { + // log warning "classic initializers will be ignored when multiple runtimes are used". + // Skipping "adjustedPath" initializer. + jsInitializer.logger?.log( + LogLevel.Warning, + `Initializer '${adjustedPath}' will be ignored because multiple runtimes are available. use 'before(web|webAssembly|server)Start' and 'after(web|webAssembly|server)Started?' instead.)` + ); + } else if (runLegacyInitializers) { + return runClassicInitializers(jsInitializer, beforeStart, afterStarted, initializerArguments); + } + + ensureInitializers(options); + + if (beforeWebAssemblyStart) { + options.webAssembly.initializers.beforeStart.push(beforeWebAssemblyStart); + } + + if (afterWebAssemblyStarted) { + options.webAssembly.initializers.afterStarted.push(afterWebAssemblyStarted); + } + + if (beforeServerStart) { + options.circuit.initializers.beforeStart.push(beforeServerStart); + } + + if (afterServerStarted) { + options.circuit.initializers.afterStarted.push(afterServerStarted); + } + + if (afterWebStarted) { + jsInitializer.afterStartedCallbacks.push(afterWebStarted); + } + + if (beforeWebStart) { + return beforeWebStart(options); + } + } + + function runClassicInitializers(jsInitializer: JSInitializer, beforeStart: BeforeBlazorStartedCallback | undefined, afterStarted: AfterBlazorStartedCallback | undefined, initializerArguments: unknown[]): void | PromiseLike { + if (afterStarted) { + jsInitializer.afterStartedCallbacks.push(afterStarted); + } + + if (beforeStart) { + return beforeStart(...initializerArguments); + } } - if (beforeStart) { - return beforeStart(...initializerArguments); + function ensureInitializers(options: Partial): + asserts options is OptionsWithInitializers { + if (!options['webAssembly']) { + options['webAssembly'] = ({ initializers: { beforeStart: [], afterStarted: [] } }) as unknown as WebAssemblyStartOptions; + } else if (!options['webAssembly'].initializers) { + options['webAssembly'].initializers = { beforeStart: [], afterStarted: [] }; + } + + if (!options['circuit']) { + options['circuit'] = ({ initializers: { beforeStart: [], afterStarted: [] } }) as unknown as CircuitStartOptions; + } else if (!options['circuit'].initializers) { + options['circuit'].initializers = { beforeStart: [], afterStarted: [] }; + } } } } async invokeAfterStartedCallbacks(blazor: typeof Blazor): Promise { - await firstRendererAttached; + const attached = getRendererAttachedPromise(this.webRendererId); + if (attached) { + await attached; + } await Promise.all(this.afterStartedCallbacks.map(callback => callback(blazor))); } } + +type OptionsWithInitializers = { + webAssembly: WebAssemblyStartOptions & { initializers: WebAssemblyInitializers }, + circuit: CircuitStartOptions & { initializers: ServerInitializers } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.WebAssembly.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.WebAssembly.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.WebAssembly.ts 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.WebAssembly.ts 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { MonoConfig } from 'dotnet'; +import { WebAssemblyStartOptions } from '../Platform/WebAssemblyStartOptions'; +import { WebRendererId } from '../Rendering/WebRendererId'; +import { JSInitializer } from './JSInitializers'; + +export async function fetchAndInvokeInitializers(options: Partial, loadedConfig: MonoConfig): Promise { + if (options.initializers) { + // Initializers were already resolved, so we don't have to fetch them, we just invoke the beforeStart ones + // and return the list of afterStarted ones so they get processed in the same way as in traditional Blazor Server. + await Promise.all(options.initializers.beforeStart.map(i => i(options))); + return new JSInitializer(/* singleRuntime: */ false, undefined, options.initializers.afterStarted, WebRendererId.WebAssembly); + } else { + const initializerArguments = [options, loadedConfig.resources?.extensions ?? {}]; + const jsInitializer = new JSInitializer( + /* singleRuntime: */ true, + undefined, + undefined, + WebRendererId.WebAssembly + ); + const initializers = Object.keys((loadedConfig?.resources?.['libraryInitializers']) || {}); + await jsInitializer.importInitializersAsync(initializers, initializerArguments); + return jsInitializer; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.Web.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.Web.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.Web.ts 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/JSInitializers/JSInitializers.Web.ts 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { Logger } from '../Platform/Logging/Logger'; +import { WebStartOptions } from '../Platform/WebStartOptions'; +import { discoverWebInitializers } from '../Services/ComponentDescriptorDiscovery'; +import { JSInitializer } from './JSInitializers'; + +export async function fetchAndInvokeInitializers(options: Partial, logger: Logger) : Promise { + const initializersElement = discoverWebInitializers(document); + if (!initializersElement) { + return new JSInitializer(false, logger); + } + const initializers: string[] = JSON.parse(atob(initializersElement)) as string[] ?? []; + const jsInitializer = new JSInitializer(false, logger); + await jsInitializer.importInitializersAsync(initializers, [options]); + return jsInitializer; +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts 2023-11-13 13:20:34.000000000 +0000 @@ -16,7 +16,7 @@ import { RenderQueue } from './RenderQueue'; import { Blazor } from '../../GlobalExports'; import { showErrorNotification } from '../../BootErrors'; -import { detachWebRendererInterop } from '../../Rendering/WebRendererInteropMethods'; +import { attachWebRendererInterop, detachWebRendererInterop } from '../../Rendering/WebRendererInteropMethods'; import { sendJSDataStream } from './CircuitStreamingInterop'; export class CircuitManager implements DotNet.DotNetCallDispatcher { @@ -34,6 +34,8 @@ private _connection?: HubConnection; + private _interopMethodsForReconnection?: DotNet.DotNetObject; + private _circuitId?: string; private _startPromise?: Promise; @@ -103,6 +105,12 @@ return false; } + for (const handler of this._options.circuitHandlers) { + if (handler.onCircuitOpened){ + handler.onCircuitOpened(); + } + } + return true; } @@ -118,7 +126,7 @@ const connection = connectionBuilder.build(); - connection.on('JS.AttachComponent', (componentId, selector) => attachRootComponentToLogicalElement(WebRendererId.Server, this.resolveElement(selector, componentId), componentId, false)); + connection.on('JS.AttachComponent', (componentId, selector) => attachRootComponentToLogicalElement(WebRendererId.Server, this.resolveElement(selector), componentId, false)); connection.on('JS.BeginInvokeJS', this._dispatcher.beginInvokeJSFromDotNet.bind(this._dispatcher)); connection.on('JS.EndInvokeDotNet', this._dispatcher.endInvokeDotNetFromJS.bind(this._dispatcher)); connection.on('JS.ReceiveByteArray', this._dispatcher.receiveByteArray.bind(this._dispatcher)); @@ -143,9 +151,18 @@ this._componentManager.onAfterRenderBatch?.(WebRendererId.Server); }); + connection.on('JS.EndUpdateRootComponents', (batchId: number) => { + this._componentManager.onAfterUpdateRootComponents?.(batchId); + }); + connection.on('JS.EndLocationChanging', Blazor._internal.navigationManager.endLocationChanging); + connection.onclose(error => { + this._interopMethodsForReconnection = detachWebRendererInterop(WebRendererId.Server); - connection.onclose(error => !this._disposed && !this._renderingFailed && this._options.reconnectionHandler!.onConnectionDown(this._options.reconnectionOptions, error)); + if (!this._disposed && !this._renderingFailed) { + this._options.reconnectionHandler!.onConnectionDown(this._options.reconnectionOptions, error); + } + }); connection.on('JS.Error', error => { this._renderingFailed = true; this.unhandledError(error); @@ -201,6 +218,11 @@ this._connection = await this.startConnection(); + if (this._interopMethodsForReconnection) { + attachWebRendererInterop(WebRendererId.Server, this._interopMethodsForReconnection); + this._interopMethodsForReconnection = undefined; + } + if (!await this._connection!.invoke('ConnectCircuit', this._circuitId)) { return false; } @@ -246,7 +268,7 @@ return sendJSDataStream(this._connection!, data, streamId, chunkSize); } - public resolveElement(sequenceOrIdentifier: string, componentId: number): LogicalElement { + public resolveElement(sequenceOrIdentifier: string): LogicalElement { // It may be a root component added by JS const jsAddedComponentContainer = getAndRemovePendingRootComponentContainer(sequenceOrIdentifier); if (jsAddedComponentContainer) { @@ -256,7 +278,7 @@ // ... or it may be a root component added by .NET const parsedSequence = Number.parseInt(sequenceOrIdentifier); if (!Number.isNaN(parsedSequence)) { - const descriptor = this._componentManager.resolveRootComponent(parsedSequence, componentId); + const descriptor = this._componentManager.resolveRootComponent(parsedSequence); return toLogicalRootCommentElement(descriptor); } @@ -319,7 +341,6 @@ await this._startPromise; this._disposed = true; - this._connection?.stop(); // Dispose the circuit on the server immediately. Closing the SignalR connection alone @@ -331,6 +352,10 @@ body: formData, }); - detachWebRendererInterop(WebRendererId.Server); + for (const handler of this._options.circuitHandlers) { + if (handler.onCircuitClosed) { + handler.onCircuitClosed(); + } + } } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/Circuits/CircuitStartOptions.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/Circuits/CircuitStartOptions.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/Circuits/CircuitStartOptions.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/Circuits/CircuitStartOptions.ts 2023-11-13 13:20:34.000000000 +0000 @@ -1,14 +1,25 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import { IBlazor } from '../../GlobalExports'; import { LogLevel } from '../Logging/Logger'; import { HubConnectionBuilder } from '@microsoft/signalr'; +export type BeforeBlazorServerStartedCallback = (options: Partial) => Promise; +export type AfterBlazorServerStartedCallback = (blazor: IBlazor) => Promise; + +export type ServerInitializers = { + beforeStart: BeforeBlazorServerStartedCallback [], + afterStarted: AfterBlazorServerStartedCallback [], +} + export interface CircuitStartOptions { configureSignalR: (builder: HubConnectionBuilder) => void; logLevel: LogLevel; reconnectionOptions: ReconnectionOptions; reconnectionHandler?: ReconnectionHandler; + initializers : ServerInitializers; + circuitHandlers: CircuitHandler[]; } export function resolveOptions(userOptions?: Partial): CircuitStartOptions { @@ -28,6 +39,11 @@ dialogId: string; } +export interface CircuitHandler { + onCircuitOpened?: () => void; + onCircuitClosed?: () => void; +} + export interface ReconnectionHandler { onConnectionDown(options: ReconnectionOptions, error?: Error): void; onConnectionUp(): void; @@ -37,6 +53,8 @@ // eslint-disable-next-line @typescript-eslint/no-empty-function configureSignalR: (_) => { }, logLevel: LogLevel.Warning, + initializers: undefined!, + circuitHandlers: [], reconnectionOptions: { maxRetries: 8, retryIntervalMilliseconds: 20000, diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/Circuits/DefaultReconnectDisplay.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/Circuits/DefaultReconnectDisplay.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/Circuits/DefaultReconnectDisplay.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/Circuits/DefaultReconnectDisplay.ts 2023-11-13 13:20:34.000000000 +0000 @@ -12,8 +12,6 @@ button: HTMLButtonElement; - addedToDom = false; - reloadParagraph: HTMLParagraphElement; loader: HTMLDivElement; @@ -85,8 +83,7 @@ } show(): void { - if (!this.addedToDom) { - this.addedToDom = true; + if (!this.document.contains(this.modal)) { this.document.body.appendChild(this.modal); } this.modal.style.display = 'block'; diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts 2023-11-13 13:20:34.000000000 +0000 @@ -12,6 +12,9 @@ import { Blazor } from '../../GlobalExports'; import { DotnetModuleConfig, EmscriptenModule, MonoConfig, ModuleAPI, RuntimeAPI, GlobalizationMode } from 'dotnet'; import { BINDINGType, MONOType } from 'dotnet/dotnet-legacy'; +import { fetchAndInvokeInitializers } from '../../JSInitializers/JSInitializers.WebAssembly'; +import { JSInitializer } from '../../JSInitializers/JSInitializers'; +import { WebRendererId } from '../../Rendering/WebRendererId'; // initially undefined and only fully initialized after createEmscriptenModuleInstance() export let BINDING: BINDINGType = undefined as any; @@ -20,6 +23,7 @@ export let dispatcher: DotNet.ICallDispatcher = undefined as any; let MONO_INTERNAL: any = undefined as any; let runtime: RuntimeAPI = undefined as any; +let jsInitializer: JSInitializer; const uint64HighOrderShift = Math.pow(2, 32); const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER @@ -38,6 +42,11 @@ function getValueFloat(ptr: number) { return MONO.getF32(ptr); } + +export function getInitializer() { + return jsInitializer; +} + function getValueU64(ptr: number) { // There is no Module.HEAPU64, and Module.getValue(..., 'i64') doesn't work because the implementation // treats 'i64' as being the same as 'i32'. Also we must take care to read both halves as unsigned. @@ -181,7 +190,7 @@ applicationEnvironment: options.environment, }; - const onConfigLoaded = async (loadedConfig: MonoConfig, { invokeLibraryInitializers }) => { + const onConfigLoaded = async (loadedConfig: MonoConfig) => { if (!loadedConfig.environmentVariables) { loadedConfig.environmentVariables = {}; } @@ -194,8 +203,7 @@ onConfigLoadedCallback?.(loadedConfig); - const initializerArguments = [options, loadedConfig.resources?.extensions ?? {}]; - await invokeLibraryInitializers('beforeStart', initializerArguments); + jsInitializer = await fetchAndInvokeInitializers(options, loadedConfig); }; const moduleConfig = (window['Module'] || {}) as typeof Module; diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/WebAssemblyComponentAttacher.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/WebAssemblyComponentAttacher.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/WebAssemblyComponentAttacher.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/WebAssemblyComponentAttacher.ts 2023-11-13 13:20:34.000000000 +0000 @@ -12,10 +12,10 @@ this.componentManager = componentManager; } - public resolveRegisteredElement(id: string, componentId: number): LogicalElement | undefined { + public resolveRegisteredElement(id: string): LogicalElement | undefined { const parsedId = Number.parseInt(id); if (!Number.isNaN(parsedId)) { - const component = this.componentManager.resolveRootComponent(parsedId, componentId); + const component = this.componentManager.resolveRootComponent(parsedId); return toLogicalRootCommentElement(component); } else { return undefined; diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/WebAssemblyStartOptions.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/WebAssemblyStartOptions.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/WebAssemblyStartOptions.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/WebAssemblyStartOptions.ts 2023-11-13 13:20:34.000000000 +0000 @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { DotnetHostBuilder, AssetBehaviors } from 'dotnet'; +import { IBlazor } from '../GlobalExports'; export interface WebAssemblyStartOptions { /** @@ -27,6 +28,8 @@ */ applicationCulture?: string; + initializers?: WebAssemblyInitializers; + /** * Allows to override .NET runtime configuration. */ @@ -37,3 +40,11 @@ // Instead, this represents the public API through which certain aspects // of boot resource loading can be customized. export type WebAssemblyBootResourceType = 'assembly' | 'pdb' | 'dotnetjs' | 'dotnetwasm' | 'globalization' | 'manifest' | 'configuration'; + +export type BeforeBlazorWebAssemblyStartedCallback = (options: Partial) => Promise; +export type AfterBlazorWebAssemblyStartedCallback = (blazor: IBlazor) => Promise; + +export type WebAssemblyInitializers = { + beforeStart: BeforeBlazorWebAssemblyStartedCallback [], + afterStarted: AfterBlazorWebAssemblyStartedCallback [], +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/WebStartOptions.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/WebStartOptions.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/WebStartOptions.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/WebStartOptions.ts 2023-11-13 13:20:34.000000000 +0000 @@ -4,9 +4,12 @@ import { WebAssemblyStartOptions } from './WebAssemblyStartOptions'; import { CircuitStartOptions } from './Circuits/CircuitStartOptions'; import { SsrStartOptions } from './SsrStartOptions'; +import { LogLevel } from './Logging/Logger'; export interface WebStartOptions { + enableClassicInitializers?: boolean; circuit: CircuitStartOptions; webAssembly: WebAssemblyStartOptions; + logLevel?: LogLevel; ssr: SsrStartOptions; } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/WebView/WebViewIpcReceiver.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/WebView/WebViewIpcReceiver.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Platform/WebView/WebViewIpcReceiver.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Platform/WebView/WebViewIpcReceiver.ts 2023-11-13 13:20:34.000000000 +0000 @@ -43,7 +43,9 @@ 'Refresh': navigationManagerFunctions.refresh, - 'SetHasLocationChangingListeners': navigationManagerFunctions.setHasLocationChangingListeners, + 'SetHasLocationChangingListeners': (hasListeners: boolean) => { + navigationManagerFunctions.setHasLocationChangingListeners(WebRendererId.WebView, hasListeners); + }, 'EndLocationChanging': navigationManagerFunctions.endLocationChanging, }; diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts 2023-11-13 13:20:34.000000000 +0000 @@ -9,11 +9,12 @@ import { applyAnyDeferredValue, tryApplySpecialProperty } from './DomSpecialPropertyUtil'; const sharedTemplateElemForParsing = document.createElement('template'); const sharedSvgElemForParsing = document.createElementNS('http://www.w3.org/2000/svg', 'g'); -const elementsToClearOnRootComponentRender: { [componentId: number]: LogicalElement } = {}; +const elementsToClearOnRootComponentRender = new Set(); const internalAttributeNamePrefix = '__internal_'; const eventPreventDefaultAttributeNamePrefix = 'preventDefault_'; const eventStopPropagationAttributeNamePrefix = 'stopPropagation_'; const interactiveRootComponentPropname = Symbol(); +const preserveContentOnDisposalPropname = Symbol(); export class BrowserRenderer { public eventDelegator: EventDelegator; @@ -47,7 +48,7 @@ // If we want to preserve existing HTML content of the root element, we don't apply the mechanism for // clearing existing children. Rendered content will then append rather than replace the existing HTML content. if (!appendContent) { - elementsToClearOnRootComponentRender[componentId] = element; + elementsToClearOnRootComponentRender.add(element); } } @@ -58,15 +59,13 @@ } // On the first render for each root component, clear any existing content (e.g., prerendered) - const rootElementToClear = elementsToClearOnRootComponentRender[componentId]; - if (rootElementToClear) { - delete elementsToClearOnRootComponentRender[componentId]; - emptyLogicalElement(rootElementToClear); + if (elementsToClearOnRootComponentRender.delete(element)) { + emptyLogicalElement(element); - if (rootElementToClear instanceof Comment) { + if (element instanceof Comment) { // We sanitize start comments by removing all the information from it now that we don't need it anymore // as it adds noise to the DOM. - rootElementToClear.textContent = '!'; + element.textContent = '!'; } } @@ -88,7 +87,12 @@ // component was added. const logicalElement = this.childComponentLocations[componentId]; markAsInteractiveRootComponentElement(logicalElement, false); - emptyLogicalElement(logicalElement); + + if (shouldPreserveContentOnInteractiveComponentDisposal(logicalElement)) { + elementsToClearOnRootComponentRender.add(logicalElement); + } else { + emptyLogicalElement(logicalElement); + } } delete this.childComponentLocations[componentId]; @@ -377,6 +381,14 @@ return element[interactiveRootComponentPropname]; } +export function setShouldPreserveContentOnInteractiveComponentDisposal(element: LogicalElement, shouldPreserve: boolean) { + element[preserveContentOnDisposalPropname] = shouldPreserve; +} + +function shouldPreserveContentOnInteractiveComponentDisposal(element: LogicalElement): boolean { + return element[preserveContentOnDisposalPropname] === true; +} + export interface ComponentDescriptor { start: Node; end: Node; @@ -388,6 +400,28 @@ return sharedSvgElemForParsing; } else { sharedTemplateElemForParsing.innerHTML = markup || ' '; + + // Since this is a markup string, we want to honor the developer's intent to + // evaluate any scripts it may contain. Scripts parsed from an innerHTML assignment + // won't be executable by default (https://stackoverflow.com/questions/1197575/can-scripts-be-inserted-with-innerhtml) + // but that's inconsistent with anything constructed from a sequence like: + // - OpenElement("script") + // - AddContent(js) or AddMarkupContent(js) + // - CloseElement() + // It doesn't make sense to have such an inconsistency in Blazor's interactive + // renderer, and for back-compat with pre-.NET 8 code (when the Razor compiler always + // used OpenElement like above), as well as consistency with static SSR, we need to make it work. + sharedTemplateElemForParsing.content.querySelectorAll('script').forEach(oldScriptElem => { + const newScriptElem = document.createElement('script'); + newScriptElem.textContent = oldScriptElem.textContent; + + oldScriptElem.getAttributeNames().forEach(attribName => { + newScriptElem.setAttribute(attribName, oldScriptElem.getAttribute(attribName)!); + }); + + oldScriptElem.parentNode!.replaceChild(newScriptElem, oldScriptElem); + }); + return sharedTemplateElemForParsing.content; } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Rendering/DomMerging/DomSync.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Rendering/DomMerging/DomSync.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Rendering/DomMerging/DomSync.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Rendering/DomMerging/DomSync.ts 2023-11-13 13:20:34.000000000 +0000 @@ -354,8 +354,10 @@ } else if (destination instanceof HTMLSelectElement && destination.selectedIndex !== value) { destination.selectedIndex = value as number; } else if (destination instanceof HTMLInputElement) { - if (destination.type === 'checkbox' && destination.checked !== value) { - destination.checked = value as boolean; + if (destination.type === 'checkbox') { + if (destination.checked !== value) { + destination.checked = value as boolean; + } } else if (destination.value !== value) { destination.value = value as string; } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Rendering/Renderer.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Rendering/Renderer.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Rendering/Renderer.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Rendering/Renderer.ts 2023-11-13 13:20:34.000000000 +0000 @@ -44,7 +44,7 @@ // 'allowExistingContents' to keep any prerendered content until we do the first client-side render // Only client-side Blazor supplies a browser renderer ID - attachRootComponentToLogicalElement(browserRendererId || 0, toLogicalElement(element, /* allow existing contents */ true), componentId, appendContent); + attachRootComponentToLogicalElement(browserRendererId, toLogicalElement(element, /* allow existing contents */ true), componentId, appendContent); } export function getRendererer(browserRendererId: number): BrowserRenderer | undefined { diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Rendering/StreamingRendering.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Rendering/StreamingRendering.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Rendering/StreamingRendering.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Rendering/StreamingRendering.ts 2023-11-13 13:20:34.000000000 +0000 @@ -60,7 +60,7 @@ // The URL was already updated on the original link click. Replace so that 'back' goes to the pre-redirection location. history.replaceState(null, '', destinationUrl); } - performEnhancedPageLoad(destinationUrl); + performEnhancedPageLoad(destinationUrl, /* interceptedLink */ false); } else { // Same reason for varying as above if (isFormPost) { diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts 2023-11-13 13:20:34.000000000 +0000 @@ -7,18 +7,21 @@ const interopMethodsByRenderer = new Map(); const rendererAttachedListeners: ((browserRendererId: number) => void)[] = []; +const rendererByIdResolverMap: Map void | undefined, Promise | undefined]> = new Map(); -let resolveFirstRendererAttached : () => void; +export function attachRendererIdResolver(rendererId: number, resolver: () => void | undefined, promise: Promise | undefined) { + rendererByIdResolverMap.set(rendererId, [resolver, promise]); +} -export const firstRendererAttached = new Promise((resolve) => { - resolveFirstRendererAttached = resolve; -}); +export function getRendererAttachedPromise(rendererId: number): Promise | undefined { + return rendererByIdResolverMap.get(rendererId)?.[1]; +} export function attachWebRendererInterop( rendererId: number, interopMethods: DotNet.DotNetObject, - jsComponentParameters: JSComponentParametersByIdentifier, - jsComponentInitializers: JSComponentIdentifiersByInitializer, + jsComponentParameters?: JSComponentParametersByIdentifier, + jsComponentInitializers?: JSComponentIdentifiersByInitializer, ): void { if (interopMethodsByRenderer.has(rendererId)) { throw new Error(`Interop methods are already registered for renderer ${rendererId}`); @@ -26,19 +29,24 @@ interopMethodsByRenderer.set(rendererId, interopMethods); - if (Object.keys(jsComponentParameters).length > 0) { + if (jsComponentParameters && jsComponentInitializers && Object.keys(jsComponentParameters).length > 0) { const manager = getInteropMethods(rendererId); enableJSRootComponents(manager, jsComponentParameters, jsComponentInitializers); } - resolveFirstRendererAttached(); + rendererByIdResolverMap.get(rendererId)?.[0]?.(); + invokeRendererAttachedListeners(rendererId); } -export function detachWebRendererInterop(rendererId: number) { - if (!interopMethodsByRenderer.delete(rendererId)) { +export function detachWebRendererInterop(rendererId: number): DotNet.DotNetObject { + const interopMethods = interopMethodsByRenderer.get(rendererId); + if (!interopMethods) { throw new Error(`Interop methods are not registered for renderer ${rendererId}`); } + + interopMethodsByRenderer.delete(rendererId); + return interopMethods; } export function isRendererAttached(browserRendererId: number): boolean { diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Services/ComponentDescriptorDiscovery.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Services/ComponentDescriptorDiscovery.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Services/ComponentDescriptorDiscovery.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Services/ComponentDescriptorDiscovery.ts 2023-11-13 13:20:34.000000000 +0000 @@ -14,20 +14,25 @@ const blazorServerStateCommentRegularExpression = /^\s*Blazor-Server-Component-State:(?[a-zA-Z0-9+/=]+)$/; const blazorWebAssemblyStateCommentRegularExpression = /^\s*Blazor-WebAssembly-Component-State:(?[a-zA-Z0-9+/=]+)$/; +const blazorWebInitializerCommentRegularExpression = /^\s*Blazor-Web-Initializers:(?[a-zA-Z0-9+/=]+)$/; export function discoverServerPersistedState(node: Node): string | null | undefined { - return discoverPersistedState(node, blazorServerStateCommentRegularExpression); + return discoverBlazorComment(node, blazorServerStateCommentRegularExpression); } export function discoverWebAssemblyPersistedState(node: Node): string | null | undefined { - return discoverPersistedState(node, blazorWebAssemblyStateCommentRegularExpression); + return discoverBlazorComment(node, blazorWebAssemblyStateCommentRegularExpression); } -function discoverPersistedState(node: Node, comment: RegExp): string | null | undefined { +export function discoverWebInitializers(node: Node): string | null | undefined { + return discoverBlazorComment(node, blazorWebInitializerCommentRegularExpression, 'initializers'); +} + +function discoverBlazorComment(node: Node, comment: RegExp, captureName = 'state'): string | null | undefined { if (node.nodeType === Node.COMMENT_NODE) { const content = node.textContent || ''; const parsedState = comment.exec(content); - const value = parsedState && parsedState.groups && parsedState.groups['state']; + const value = parsedState && parsedState.groups && parsedState.groups[captureName]; if (value){ node.parentNode?.removeChild(node); } @@ -41,7 +46,7 @@ const nodes = node.childNodes; for (let index = 0; index < nodes.length; index++) { const candidate = nodes[index]; - const result = discoverPersistedState(candidate, comment); + const result = discoverBlazorComment(candidate, comment, captureName); if (result){ return result; } @@ -294,12 +299,17 @@ } as unknown as ComponentMarker; } -export function canMergeDescriptors(target: ComponentDescriptor, source: ComponentDescriptor): boolean { - if (target.type !== source.type || target.key !== source.key) { +function doKeysMatch(a: MarkerKey | undefined, b: MarkerKey | undefined) { + if (!a || !b) { + // Unspecified keys are never considered to be matching return false; } - return true; + return a.locationHash === b.locationHash && a.formattedComponentKey === b.formattedComponentKey; +} + +export function canMergeDescriptors(target: ComponentDescriptor, source: ComponentDescriptor): boolean { + return target.type === source.type && doKeysMatch(target.key, source.key); } export function mergeDescriptors(target: ComponentDescriptor, source: ComponentDescriptor) { @@ -354,7 +364,12 @@ type CommonMarkerData = { type: string; prerenderId?: string; - key?: string; + key?: MarkerKey; +} + +type MarkerKey = { + locationHash: string; + formattedComponentKey?: string; } type ServerMarkerData = { diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Services/InitialRootComponentsList.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Services/InitialRootComponentsList.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Services/InitialRootComponentsList.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Services/InitialRootComponentsList.ts 2023-11-13 13:20:34.000000000 +0000 @@ -8,7 +8,7 @@ constructor(public readonly initialComponents: ComponentDescriptorType[]) { } - resolveRootComponent(selectorId: number, _componentId: number): ComponentDescriptor { - return this.initialComponents[selectorId]; + resolveRootComponent(ssrComponentId: number): ComponentDescriptor { + return this.initialComponents[ssrComponentId]; } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Services/NavigationEnhancement.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Services/NavigationEnhancement.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Services/NavigationEnhancement.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Services/NavigationEnhancement.ts 2023-11-13 13:20:34.000000000 +0000 @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { synchronizeDomContent } from '../Rendering/DomMerging/DomSync'; -import { attachProgrammaticEnhancedNavigationHandler, handleClickForNavigationInterception, hasInteractiveRouter } from './NavigationUtils'; +import { attachProgrammaticEnhancedNavigationHandler, handleClickForNavigationInterception, hasInteractiveRouter, notifyEnhancedNavigationListners } from './NavigationUtils'; /* In effect, we have two separate client-side navigation mechanisms: @@ -35,6 +35,10 @@ let navigationEnhancementCallbacks: NavigationEnhancementCallbacks; let performingEnhancedPageLoad: boolean; +// This gets initialized to the current URL when we load. +// After that, it gets updated every time we successfully complete a navigation. +let currentContentUrl = location.href; + export interface NavigationEnhancementCallbacks { documentUpdated: () => void; enhancedNavigationCompleted: () => void; @@ -70,7 +74,7 @@ history.pushState(null, /* ignored title */ '', absoluteInternalHref); } - performEnhancedPageLoad(absoluteInternalHref); + performEnhancedPageLoad(absoluteInternalHref, /* interceptedLink */ false); } function onDocumentClick(event: MouseEvent) { @@ -78,13 +82,13 @@ return; } - if (event.target instanceof HTMLAnchorElement && !enhancedNavigationIsEnabledForLink(event.target)) { + if (event.target instanceof HTMLElement && !enhancedNavigationIsEnabledForElement(event.target)) { return; } handleClickForNavigationInterception(event, absoluteInternalHref => { history.pushState(null, /* ignored title */ '', absoluteInternalHref); - performEnhancedPageLoad(absoluteInternalHref); + performEnhancedPageLoad(absoluteInternalHref, /* interceptedLink */ true); }); } @@ -93,7 +97,7 @@ return; } - performEnhancedPageLoad(location.href); + performEnhancedPageLoad(location.href, /* interceptedLink */ false); } function onDocumentSubmit(event: SubmitEvent) { @@ -126,20 +130,28 @@ if (fetchOptions.method === 'get') { // method is always returned as lowercase url.search = new URLSearchParams(formData as any).toString(); + + // For forms with method=get, we need to push a URL history entry equivalent to how it + // would be pushed for a native
    submission. This is also equivalent to + // how we push a URL history entry before starting enhanced page load on an click. + history.pushState(null, /* ignored title */ '', url.toString()); } else { fetchOptions.body = formData; } - performEnhancedPageLoad(url.toString(), fetchOptions); + performEnhancedPageLoad(url.toString(), /* interceptedLink */ false, fetchOptions); } } -export async function performEnhancedPageLoad(internalDestinationHref: string, fetchOptions?: RequestInit) { +export async function performEnhancedPageLoad(internalDestinationHref: string, interceptedLink: boolean, fetchOptions?: RequestInit) { performingEnhancedPageLoad = true; // First, stop any preceding enhanced page load currentEnhancedNavigationAbortController?.abort(); + // Notify any interactive runtimes that an enhanced navigation is starting + notifyEnhancedNavigationListners(internalDestinationHref, interceptedLink); + // Now request the new page via fetch, and a special header that tells the server we want it to inject // framing boundaries to distinguish the initial document and each subsequent streaming SSR update. currentEnhancedNavigationAbortController = new AbortController(); @@ -150,9 +162,10 @@ headers: { // Because of no-cors, we can only send CORS-safelisted headers, so communicate the info about // enhanced nav as a MIME type parameter - 'accept': 'text/html;blazor-enhanced-nav=on', + 'accept': 'text/html; blazor-enhanced-nav=on', }, }, fetchOptions)); + let isNonRedirectedPostToADifferentUrlMessage: string | null = null; await getResponsePartsWithFraming(responsePromise, abortSignal, (response, initialContent) => { const isGetRequest = !fetchOptions?.method || fetchOptions.method === 'get'; @@ -211,6 +224,25 @@ return; } + if (!response.redirected && !isGetRequest && isSuccessResponse) { + // If this is the result of a form post that didn't trigger a redirection. + if (!isForSamePath(response)) { + // In this case we don't want to push the currentContentUrl to the history stack because we don't know if this is a location + // we can navigate back to (as we don't know if the location supports GET) and we are not able to replicate the Resubmit form? + // browser behavior. + // The only case where this is acceptable is when the last content URL, is the same as the URL for the form we posted to. + isNonRedirectedPostToADifferentUrlMessage = `Cannot perform enhanced form submission that changes the URL (except via a redirection), because then back/forward would not work. Either remove this form\'s \'action\' attribute, or change its method to \'get\', or do not mark it as enhanced.\nOld URL: ${location.href}\nNew URL: ${response.url}`; + } else { + if (location.href !== currentContentUrl) { + // The url on the browser might be out of data, so push an entry to the stack to update the url in place. + history.pushState(null, '', currentContentUrl); + } + } + } + + // Set the currentContentUrl to the location of the last completed navigation. + currentContentUrl = response.url; + const responseContentType = response.headers.get('content-type'); if (responseContentType?.startsWith('text/html') && initialContent) { // For HTML responses, regardless of the status code, display it @@ -256,6 +288,30 @@ performingEnhancedPageLoad = false; navigationEnhancementCallbacks.enhancedNavigationCompleted(); + + // For non-GET requests, the destination has to be the same URL you're already on, or result in a redirection + // (post/redirect/get). You're not allowed to POST to a different URL without redirecting, because then back/forwards + // won't work - we can't recreate the "Resubmit form?" behavior. + // See https://github.com/dotnet/aspnetcore/issues/50945 + // The reason we delay throwing until after SSR completes is that SSR might include a redirection signal. If we get + // here without navigating away, it's an error. + if (isNonRedirectedPostToADifferentUrlMessage) { + throw new Error(isNonRedirectedPostToADifferentUrlMessage); + } + } + + function isForSamePath(response: Response) { + // We are trying to determine if the response URL is compatible with the last content URL that was successfully loaded on to + // the page. + // We are going to use the scheme, host, port and path to determine if they are compatible. We do not account for the query string + // as we want to allow for the query string to change. (Blazor doesn't use the query string for routing purposes). + + const responseUrl = new URL(response.url); + const currentContentUrlParsed = new URL(currentContentUrl!); + return responseUrl.protocol === currentContentUrlParsed.protocol + && responseUrl.host === currentContentUrlParsed.host + && responseUrl.port === currentContentUrlParsed.port + && responseUrl.pathname === currentContentUrlParsed.pathname; } } @@ -335,7 +391,7 @@ }); } -function enhancedNavigationIsEnabledForLink(element: HTMLAnchorElement): boolean { +function enhancedNavigationIsEnabledForElement(element: HTMLElement): boolean { // For links, they default to being enhanced, but you can override at any ancestor level (both positively and negatively) const closestOverride = element.closest('[data-enhance-nav]'); if (closestOverride) { @@ -349,7 +405,7 @@ function enhancedNavigationIsEnabledForForm(form: HTMLFormElement): boolean { // For forms, they default *not* to being enhanced, and must be enabled explicitly on the form element itself (not an ancestor). const attributeValue = form.getAttribute('data-enhance'); - return typeof(attributeValue) === 'string' + return typeof (attributeValue) === 'string' && attributeValue === '' || attributeValue?.toLowerCase() === 'true'; } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Services/NavigationManager.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Services/NavigationManager.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Services/NavigationManager.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Services/NavigationManager.ts 2023-11-13 13:20:34.000000000 +0000 @@ -4,16 +4,22 @@ import '@microsoft/dotnet-js-interop'; import { resetScrollAfterNextBatch } from '../Rendering/Renderer'; import { EventDelegator } from '../Rendering/Events/EventDelegator'; -import { handleClickForNavigationInterception, hasInteractiveRouter, hasProgrammaticEnhancedNavigationHandler, isWithinBaseUriSpace, performProgrammaticEnhancedNavigation, setHasInteractiveRouter, toAbsoluteUri } from './NavigationUtils'; +import { attachEnhancedNavigationListener, getInteractiveRouterRendererId, handleClickForNavigationInterception, hasInteractiveRouter, hasProgrammaticEnhancedNavigationHandler, isWithinBaseUriSpace, performProgrammaticEnhancedNavigation, setHasInteractiveRouter, toAbsoluteUri } from './NavigationUtils'; +import { WebRendererId } from '../Rendering/WebRendererId'; +import { isRendererAttached } from '../Rendering/WebRendererInteropMethods'; let hasRegisteredNavigationEventListeners = false; -let hasLocationChangingEventListeners = false; let currentHistoryIndex = 0; let currentLocationChangingCallId = 0; -// Will be initialized once someone registers -let notifyLocationChangedCallback: ((uri: string, state: string | undefined, intercepted: boolean) => Promise) | null = null; -let notifyLocationChangingCallback: ((callId: number, uri: string, state: string | undefined, intercepted: boolean) => Promise) | null = null; +type NavigationCallbacks = { + rendererId: WebRendererId; + hasLocationChangingEventListeners: boolean; + locationChanged(uri: string, state: string | undefined, intercepted: boolean): Promise; + locationChanging(callId: number, uri: string, state: string | undefined, intercepted: boolean): Promise; +}; + +const navigationCallbacks = new Map(); let popStateCallback: ((state: PopStateEvent) => Promise | void) = onBrowserInitiatedPopState; let resolveCurrentNavigation: ((shouldContinueNavigation: boolean) => void) | null = null; @@ -32,11 +38,16 @@ }; function listenForNavigationEvents( + rendererId: WebRendererId, locationChangedCallback: (uri: string, state: string | undefined, intercepted: boolean) => Promise, locationChangingCallback: (callId: number, uri: string, state: string | undefined, intercepted: boolean) => Promise ): void { - notifyLocationChangedCallback = locationChangedCallback; - notifyLocationChangingCallback = locationChangingCallback; + navigationCallbacks.set(rendererId, { + rendererId, + hasLocationChangingEventListeners: false, + locationChanged: locationChangedCallback, + locationChanging: locationChangingCallback, + }); if (hasRegisteredNavigationEventListeners) { return; @@ -45,10 +56,18 @@ hasRegisteredNavigationEventListeners = true; window.addEventListener('popstate', onPopState); currentHistoryIndex = history.state?._index ?? 0; + + attachEnhancedNavigationListener((internalDestinationHref, interceptedLink) => { + notifyLocationChanged(interceptedLink, internalDestinationHref); + }); } -function setHasLocationChangingListeners(hasListeners: boolean) { - hasLocationChangingEventListeners = hasListeners; +function setHasLocationChangingListeners(rendererId: WebRendererId, hasListeners: boolean) { + const callbacks = navigationCallbacks.get(rendererId); + if (!callbacks) { + throw new Error(`Renderer with ID '${rendererId}' is not listening for navigation events`); + } + callbacks.hasLocationChangingEventListeners = hasListeners; } export function scrollToElement(identifier: string): boolean { @@ -162,8 +181,9 @@ return; } - if (!skipLocationChangingCallback && hasLocationChangingEventListeners) { - const shouldContinueNavigation = await notifyLocationChanging(absoluteInternalHref, state, interceptedLink); + const callbacks = getInteractiveRouterNavigationCallbacks(); + if (!skipLocationChangingCallback && callbacks?.hasLocationChangingEventListeners) { + const shouldContinueNavigation = await notifyLocationChanging(absoluteInternalHref, state, interceptedLink, callbacks); if (!shouldContinueNavigation) { return; } @@ -216,18 +236,12 @@ } } -function notifyLocationChanging(uri: string, state: string | undefined, intercepted: boolean): Promise { +function notifyLocationChanging(uri: string, state: string | undefined, intercepted: boolean, callbacks: NavigationCallbacks): Promise { return new Promise(resolve => { ignorePendingNavigation(); - - if (!notifyLocationChangingCallback) { - resolve(false); - return; - } - currentLocationChangingCallId++; resolveCurrentNavigation = resolve; - notifyLocationChangingCallback(currentLocationChangingCallId, uri, state, intercepted); + callbacks.locationChanging(currentLocationChangingCallId, uri, state, intercepted); }); } @@ -241,7 +255,8 @@ async function onBrowserInitiatedPopState(state: PopStateEvent) { ignorePendingNavigation(); - if (hasLocationChangingEventListeners) { + const callbacks = getInteractiveRouterNavigationCallbacks(); + if (callbacks?.hasLocationChangingEventListeners) { const index = state.state?._index ?? 0; const userState = state.state?.userState; const delta = index - currentHistoryIndex; @@ -250,7 +265,7 @@ // Temporarily revert the navigation until we confirm if the navigation should continue. await navigateHistoryWithoutPopStateCallback(-delta); - const shouldContinueNavigation = await notifyLocationChanging(uri, userState, false); + const shouldContinueNavigation = await notifyLocationChanging(uri, userState, false, callbacks); if (!shouldContinueNavigation) { return; } @@ -261,10 +276,14 @@ await notifyLocationChanged(false); } -async function notifyLocationChanged(interceptedLink: boolean) { - if (notifyLocationChangedCallback) { - await notifyLocationChangedCallback(location.href, history.state?.userState, interceptedLink); - } +async function notifyLocationChanged(interceptedLink: boolean, internalDestinationHref?: string) { + const uri = internalDestinationHref ?? location.href; + + await Promise.all(Array.from(navigationCallbacks, async ([rendererId, callbacks]) => { + if (isRendererAttached(rendererId)) { + await callbacks.locationChanged(uri, history.state?.userState, interceptedLink); + } + })); } async function onPopState(state: PopStateEvent) { @@ -275,6 +294,15 @@ currentHistoryIndex = history.state?._index ?? 0; } +function getInteractiveRouterNavigationCallbacks(): NavigationCallbacks | undefined { + const interactiveRouterRendererId = getInteractiveRouterRendererId(); + if (interactiveRouterRendererId === undefined) { + return undefined; + } + + return navigationCallbacks.get(interactiveRouterRendererId); +} + function shouldUseClientSideRouting() { return hasInteractiveRouter() || !hasProgrammaticEnhancedNavigationHandler(); } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Services/NavigationUtils.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Services/NavigationUtils.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Services/NavigationUtils.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Services/NavigationUtils.ts 2023-11-13 13:20:34.000000000 +0000 @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -let hasInteractiveRouterValue = false; +import { WebRendererId } from '../Rendering/WebRendererId'; + +let interactiveRouterRendererId: WebRendererId | undefined = undefined; let programmaticEnhancedNavigationHandler: typeof performProgrammaticEnhancedNavigation | undefined; +let enhancedNavigationListener: typeof notifyEnhancedNavigationListners | undefined; /** * Checks if a click event corresponds to an tag referencing a URL within the base href, and that interception @@ -44,6 +47,14 @@ && (nextChar === '' || nextChar === '/' || nextChar === '?' || nextChar === '#'); } +export function attachEnhancedNavigationListener(listener: typeof enhancedNavigationListener) { + enhancedNavigationListener = listener; +} + +export function notifyEnhancedNavigationListners(internalDestinationHref: string, interceptedLink: boolean) { + enhancedNavigationListener?.(internalDestinationHref, interceptedLink); +} + export function hasProgrammaticEnhancedNavigationHandler(): boolean { return programmaticEnhancedNavigationHandler !== undefined; } @@ -107,16 +118,24 @@ function findClosestAnchorAncestorLegacy(element: Element | null, tagName: string) { return !element - ? null - : element.tagName === tagName - ? element - : findClosestAnchorAncestorLegacy(element.parentElement, tagName); + ? null + : element.tagName === tagName + ? element + : findClosestAnchorAncestorLegacy(element.parentElement, tagName); } export function hasInteractiveRouter(): boolean { - return hasInteractiveRouterValue; + return interactiveRouterRendererId !== undefined; } -export function setHasInteractiveRouter() { - hasInteractiveRouterValue = true; +export function getInteractiveRouterRendererId() : WebRendererId | undefined { + return interactiveRouterRendererId; +} + +export function setHasInteractiveRouter(rendererId: WebRendererId) { + if (interactiveRouterRendererId !== undefined && interactiveRouterRendererId !== rendererId) { + throw new Error('Only one interactive runtime may enable navigation interception at a time.'); + } + + interactiveRouterRendererId = rendererId; } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Services/RootComponentManager.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Services/RootComponentManager.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Services/RootComponentManager.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Services/RootComponentManager.ts 2023-11-13 13:20:34.000000000 +0000 @@ -3,8 +3,9 @@ import { ComponentDescriptor } from './ComponentDescriptorDiscovery'; -export interface RootComponentManager { - initialComponents: ComponentDescriptorType[]; +export interface RootComponentManager { + initialComponents: InitialComponentsDescriptorType[]; onAfterRenderBatch?(browserRendererId: number): void; - resolveRootComponent(selectorId: number, componentId: number): ComponentDescriptor; + onAfterUpdateRootComponents?(batchId: number): void; + resolveRootComponent(ssrComponentId: number): ComponentDescriptor; } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Services/WebRootComponentManager.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Services/WebRootComponentManager.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/src/Services/WebRootComponentManager.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/src/Services/WebRootComponentManager.ts 2023-11-13 13:20:34.000000000 +0000 @@ -12,39 +12,51 @@ import { Blazor } from '../GlobalExports'; import { getRendererer } from '../Rendering/Renderer'; import { isPageLoading } from './NavigationEnhancement'; +import { setShouldPreserveContentOnInteractiveComponentDisposal } from '../Rendering/BrowserRenderer'; +import { LogicalElement } from '../Rendering/LogicalElements'; + +type RootComponentOperationBatch = { + batchId: number; + operations: RootComponentOperation[]; +} type RootComponentOperation = RootComponentAddOperation | RootComponentUpdateOperation | RootComponentRemoveOperation; type RootComponentAddOperation = { type: 'add'; - selectorId: number; + ssrComponentId: number; marker: ComponentMarker; }; type RootComponentUpdateOperation = { type: 'update'; - componentId: number; + ssrComponentId: number; marker: ComponentMarker; }; type RootComponentRemoveOperation = { type: 'remove'; - componentId: number; + ssrComponentId: number; }; type RootComponentInfo = { descriptor: ComponentDescriptor; + ssrComponentId: number; assignedRendererId?: WebRendererId; uniqueIdAtLastUpdate?: number; - interactiveComponentId?: number; + hasPendingRemoveOperation?: boolean; }; -export class WebRootComponentManager implements DescriptorHandler, RootComponentManager { - private readonly _rootComponents = new Set(); +export class WebRootComponentManager implements DescriptorHandler, RootComponentManager { + private readonly _rootComponentsBySsrComponentId = new Map(); + + private readonly _seenDescriptors = new Set(); - private readonly _descriptors = new Set(); + private readonly _pendingOperationBatches: { [batchId: number]: RootComponentOperationBatch } = {}; - private readonly _pendingComponentsToResolve = new Map(); + private _nextOperationBatchId = 1; + + private _nextSsrComponentId = 1; private _didWebAssemblyFailToLoadQuickly = false; @@ -84,7 +96,7 @@ } public registerComponent(descriptor: ComponentDescriptor) { - if (this._descriptors.has(descriptor)) { + if (this._seenDescriptors.has(descriptor)) { return; } @@ -96,13 +108,16 @@ this.startLoadingWebAssemblyIfNotStarted(); } - this._descriptors.add(descriptor); - this._rootComponents.add({ descriptor }); + const ssrComponentId = this._nextSsrComponentId++; + + this._seenDescriptors.add(descriptor); + this._rootComponentsBySsrComponentId.set(ssrComponentId, { descriptor, ssrComponentId }); } private unregisterComponent(component: RootComponentInfo) { - this._descriptors.delete(component.descriptor); - this._rootComponents.delete(component); + this._seenDescriptors.delete(component.descriptor); + this._rootComponentsBySsrComponentId.delete(component.ssrComponentId); + this.circuitMayHaveNoRootComponents(); } private async startLoadingWebAssemblyIfNotStarted() { @@ -188,7 +203,7 @@ // refreshRootComponents. setTimeout(() => { this._isComponentRefreshPending = false; - this.refreshRootComponents(this._rootComponents); + this.refreshRootComponents(this._rootComponentsBySsrComponentId.values()); }, 0); } @@ -226,7 +241,7 @@ } // We consider SSR'd components on the page that may get activated using the specified renderer. - for (const { descriptor: { type }, assignedRendererId } of this._rootComponents) { + for (const { descriptor: { type }, assignedRendererId } of this._rootComponentsBySsrComponentId.values()) { if (assignedRendererId === rendererId) { // The component has been assigned to use the specified renderer. return true; @@ -271,15 +286,19 @@ } for (const [rendererId, operations] of operationsByRendererId) { - const operationsJson = JSON.stringify(operations); + const batch: RootComponentOperationBatch = { + batchId: this._nextOperationBatchId++, + operations, + }; + this._pendingOperationBatches[batch.batchId] = batch; + const batchJson = JSON.stringify(batch); + if (rendererId === WebRendererId.Server) { - updateServerRootComponents(operationsJson); + updateServerRootComponents(batchJson); } else { - this.updateWebAssemblyRootComponents(operationsJson); + this.updateWebAssemblyRootComponents(batchJson); } } - - this.circuitMayHaveNoRootComponents(); } private updateWebAssemblyRootComponents(operationsJson: string) { @@ -354,68 +373,88 @@ return null; } + // .NET may dispose and re-initialize the interactive component as a result of a future 'update' operation. + // This call prevents the component's content from being deleted from the DOM between the disposal + // and subsequent re-initialization. + setShouldPreserveContentOnInteractiveComponentDisposal(component.descriptor.start as unknown as LogicalElement, true); + component.assignedRendererId = rendererId; component.uniqueIdAtLastUpdate = component.descriptor.uniqueId; - this._pendingComponentsToResolve.set(component.descriptor.uniqueId, component); - return { type: 'add', selectorId: component.descriptor.uniqueId, marker: descriptorToMarker(component.descriptor) }; - } + return { type: 'add', ssrComponentId: component.ssrComponentId, marker: descriptorToMarker(component.descriptor) }; + } else { + if (!isRendererAttached(component.assignedRendererId)) { + // The renderer for this descriptor is not attached, so we'll no-op. + // After the renderer attaches, we'll handle this descriptor again if + // it's still in the document. + return null; + } - if (component.uniqueIdAtLastUpdate === component.descriptor.uniqueId) { - // The descriptor has not changed since the last update. - // Nothing to do. - return null; - } + // The component has already been added for interactivity. + if (component.uniqueIdAtLastUpdate === component.descriptor.uniqueId) { + // The descriptor has not changed since the last update. + // Nothing to do. + return null; + } - if (component.interactiveComponentId !== undefined) { - // The component has become interactive, so we'll update its parameters. + // The descriptor has changed since it was last updated, so we'll update the component's parameters. component.uniqueIdAtLastUpdate = component.descriptor.uniqueId; - return { type: 'update', componentId: component.interactiveComponentId, marker: descriptorToMarker(component.descriptor) }; + return { type: 'update', ssrComponentId: component.ssrComponentId, marker: descriptorToMarker(component.descriptor) }; } - - // We have started to add the component, but it has not become interactive yet. - // We'll wait until we have a component ID to work with before sending parameter - // updates. } else { - this.unregisterComponent(component); - if (component.assignedRendererId !== undefined && component.interactiveComponentId !== undefined) { - const renderer = getRendererer(component.assignedRendererId); - renderer?.disposeComponent(component.interactiveComponentId); + if (component.hasPendingRemoveOperation) { + // The component is already being disposed, so there's nothing left to do. + return null; + } + + if (component.assignedRendererId === undefined) { + // The component was removed from the document before it was assigned to a renderer, + // so we don't have to notify .NET that anything has changed. + this.unregisterComponent(component); + return null; } - if (component.interactiveComponentId !== undefined) { - // We have an interactive component for this marker, so we'll remove it. - return { type: 'remove', componentId: component.interactiveComponentId }; + if (!isRendererAttached(component.assignedRendererId)) { + // The component was already assigned a renderer, but that renderer is no longer attached. + // After the renderer attaches, we'll handle the removal of this descriptor again. + return null; } - // If we make it here, that means we either: - // 1. Haven't started to make the component interactive, in which case we have no further action to take. - // 2. Have started to make the component interactive, but it hasn't become interactive yet. In this case, - // we'll wait to remove the component until after we have a component ID to provide. - } + // Since the component will be getting completedly diposed from .NET (rather than replaced by another component, which can + // happen as a result of an 'update' operation), we indicate that its content should no longer be preserved on disposal. + setShouldPreserveContentOnInteractiveComponentDisposal(component.descriptor.start as unknown as LogicalElement, false); - return null; + // This component was removed from the document and we've assigned a renderer ID, + // so we'll dispose it in .NET. + component.hasPendingRemoveOperation = true; + return { type: 'remove', ssrComponentId: component.ssrComponentId }; + } } - public resolveRootComponent(selectorId: number, componentId: number): ComponentDescriptor { - const component = this._pendingComponentsToResolve.get(selectorId); + public resolveRootComponent(ssrComponentId: number): ComponentDescriptor { + const component = this._rootComponentsBySsrComponentId.get(ssrComponentId); if (!component) { - throw new Error(`Could not resolve a root component for descriptor with ID '${selectorId}'.`); + throw new Error(`Could not resolve a root component with SSR component ID '${ssrComponentId}'.`); } - this._pendingComponentsToResolve.delete(selectorId); + return component.descriptor; + } - if (component.interactiveComponentId !== undefined) { - throw new Error('Cannot resolve a root component for the same descriptor multiple times.'); + public onAfterUpdateRootComponents(batchId: number): void { + const batch = this._pendingOperationBatches[batchId]; + delete this._pendingOperationBatches[batchId]; + + for (const operation of batch.operations) { + switch (operation.type) { + case 'remove': { + // We can stop tracking this component now that .NET has acknowedged its removal. + const component = this._rootComponentsBySsrComponentId.get(operation.ssrComponentId); + if (component) { + this.unregisterComponent(component); + } + break; + } + } } - - component.interactiveComponentId = componentId; - - // The descriptor may have changed since the last call to handleUpdatedRootComponentsCore(). - // We'll update this single descriptor so that the component receives the most up-to-date parameters - // or gets removed if it no longer exists on the page. - this.refreshRootComponents([component]); - - return component.descriptor; } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/test/DomSync.test.ts dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/test/DomSync.test.ts --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Components/Web.JS/test/DomSync.test.ts 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Components/Web.JS/test/DomSync.test.ts 2023-11-13 13:20:34.000000000 +0000 @@ -469,6 +469,22 @@ expect(selectElem.selectedIndex).toBe(2); }); + test('should handle checkboxes with value attribute', () => { + // Checkboxes require even more special-case handling because their 'value' attribute + // has to be handled as a regular attribute, and 'checked' must be handled similarly + // to 'value' on other inputs + + const destination = makeExistingContent(``); + const newContent = makeNewContent(``); + + const checkboxElem = destination.startExclusive.nextSibling as HTMLInputElement; + + // Act/Assert + synchronizeDomContent(destination, newContent); + expect(checkboxElem.checked).toBeTruthy(); + expect(checkboxElem.value).toBe('second'); + }); + test('should treat doctype nodes as unchanged', () => { // Can't update a doctype after the document is created, nor is there a use case for doing so // We just have to skip them, as it would be an error to try removing or inserting them diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Hosting/Hosting/src/Internal/HostingMetrics.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Hosting/Hosting/src/Internal/HostingMetrics.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Hosting/Hosting/src/Internal/HostingMetrics.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Hosting/Hosting/src/Internal/HostingMetrics.cs 2023-11-13 13:20:34.000000000 +0000 @@ -24,12 +24,12 @@ _activeRequestsCounter = _meter.CreateUpDownCounter( "http.server.active_requests", unit: "{request}", - description: "Number of HTTP requests that are currently active on the server."); + description: "Number of active HTTP server requests."); _requestDuration = _meter.CreateHistogram( "http.server.request.duration", unit: "s", - description: "Measures the duration of inbound HTTP requests."); + description: "Duration of HTTP server requests."); } // Note: Calling code checks whether counter is enabled. @@ -54,7 +54,6 @@ if (_requestDuration.Enabled) { - tags.Add("network.protocol.name", "http"); if (TryGetHttpVersion(protocol, out var httpVersion)) { tags.Add("network.protocol.version", httpVersion); @@ -71,10 +70,10 @@ tags.Add("http.route", route); } // This exception is only present if there is an unhandled exception. - // An exception caught by ExceptionHandlerMiddleware and DeveloperExceptionMiddleware isn't thrown to here. Instead, those middleware add exception.type to custom tags. + // An exception caught by ExceptionHandlerMiddleware and DeveloperExceptionMiddleware isn't thrown to here. Instead, those middleware add error.type to custom tags. if (exception != null) { - tags.Add("exception.type", exception.GetType().FullName); + tags.Add("error.type", exception.GetType().FullName); } if (customTags != null) { diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs 2023-11-13 13:20:34.000000000 +0000 @@ -10,9 +10,9 @@ using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Diagnostics.Metrics; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; -using Microsoft.Extensions.Telemetry.Testing.Metrics; using Moq; namespace Microsoft.AspNetCore.Hosting.Tests; diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Hosting/Hosting/test/HostingMetricsTests.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Hosting/Hosting/test/HostingMetricsTests.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Hosting/Hosting/test/HostingMetricsTests.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Hosting/Hosting/test/HostingMetricsTests.cs 2023-11-13 13:20:34.000000000 +0000 @@ -9,8 +9,8 @@ using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Telemetry.Testing.Metrics; namespace Microsoft.AspNetCore.Hosting.Tests; @@ -92,16 +92,15 @@ static void AssertRequestDuration(CollectedMeasurement measurement, string httpVersion, int statusCode, string exceptionName = null, bool? unhandledRequest = null) { Assert.True(measurement.Value > 0); - Assert.Equal("http", (string)measurement.Tags["network.protocol.name"]); Assert.Equal(httpVersion, (string)measurement.Tags["network.protocol.version"]); Assert.Equal(statusCode, (int)measurement.Tags["http.response.status_code"]); if (exceptionName == null) { - Assert.False(measurement.Tags.ContainsKey("exception.type")); + Assert.False(measurement.Tags.ContainsKey("error.type")); } else { - Assert.Equal(exceptionName, (string)measurement.Tags["exception.type"]); + Assert.Equal(exceptionName, (string)measurement.Tags["error.type"]); } if (unhandledRequest ?? false) { diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Hosting/Hosting/test/Microsoft.AspNetCore.Hosting.Tests.csproj dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Hosting/Hosting/test/Microsoft.AspNetCore.Hosting.Tests.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Hosting/Hosting/test/Microsoft.AspNetCore.Hosting.Tests.csproj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Hosting/Hosting/test/Microsoft.AspNetCore.Hosting.Tests.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -24,7 +24,7 @@ - + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/InvocationOperationExtensions.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/InvocationOperationExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/InvocationOperationExtensions.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/InvocationOperationExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -27,6 +27,8 @@ { invocationOperation = null; if (operation is IInvocationOperation targetOperation && + targetOperation.TargetMethod.ContainingNamespace is { Name: "Builder", ContainingNamespace: { Name: "AspNetCore", ContainingNamespace: { Name: "Microsoft", ContainingNamespace.IsGlobalNamespace: true } } } && + targetOperation.TargetMethod.ContainingAssembly.Name is "Microsoft.AspNetCore.Routing" && targetOperation.TryGetRouteHandlerArgument(out var routeHandlerParameter) && routeHandlerParameter is { Parameter.Type: {} delegateType } && SymbolEqualityComparer.Default.Equals(delegateType, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Delegate))) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeCreationTests.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeCreationTests.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeCreationTests.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeCreationTests.cs 2023-11-13 13:20:34.000000000 +0000 @@ -556,4 +556,75 @@ Assert.Equal(new EventId(5, "ImplicitBodyNotProvided"), log2.EventId); Assert.Equal(@"Implicit body inferred for parameter ""todo1"" but no body was provided. Did you mean to use a Service instead?", log2.Message); } + + [Fact] + public async Task SkipsMapWithIncorrectNamespaceAndAssembly() + { + var source = """ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; + +namespace TestApp +{ + public static class TestMapActions + { + public static IEndpointRouteBuilder MapTestEndpoints(this IEndpointRouteBuilder app) + { + app.ServiceProvider.Map(1, (string test) => "Hello world!"); + app.ServiceProvider.MapPost(2, (string test) => "Hello world!"); + app.Map(3, (string test) => "Hello world!"); + app.MapPost(4, (string test) => "Hello world!"); + return app; + } + } + + public static class EndpointRouteBuilderExtensions + { + public static IServiceProvider Map(this IServiceProvider app, int id, Delegate requestDelegate) + { + return app; + } + + public static IEndpointRouteBuilder Map(this IEndpointRouteBuilder app, int id, Delegate requestDelegate) + { + return app; + } + } +} +namespace Microsoft.AspNetCore.Builder +{ + public static class EndpointRouteBuilderExtensions + { + public static IServiceProvider MapPost(this IServiceProvider app, int id, Delegate requestDelegate) + { + return app; + } + + public static IEndpointRouteBuilder MapPost(this IEndpointRouteBuilder app, int id, Delegate requestDelegate) + { + return app; + } + } +} +"""; + var project = CreateProject(); + project = project.AddDocument("TestMapActions.cs", SourceText.From(source, Encoding.UTF8)).Project; + var compilation = await project.GetCompilationAsync(); + + var generator = new RequestDelegateGenerator.RequestDelegateGenerator().AsSourceGenerator(); + GeneratorDriver driver = CSharpGeneratorDriver.Create(generators: new[] + { + generator + }, + driverOptions: new GeneratorDriverOptions(IncrementalGeneratorOutputKind.None, trackIncrementalGeneratorSteps: true), + parseOptions: ParseOptions); + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var updatedCompilation, + out var diagnostics); + var generatorRunResult = driver.GetRunResult(); + + // Emits diagnostic and generates source for all endpoints + var result = Assert.IsType(Assert.Single(generatorRunResult.Results)); + Assert.Empty(GetStaticEndpoints(result, GeneratorSteps.EndpointModelStep)); + } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeIncrementalityTests.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeIncrementalityTests.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeIncrementalityTests.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeIncrementalityTests.cs 2023-11-13 13:20:34.000000000 +0000 @@ -1,5 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Immutable; using Microsoft.AspNetCore.Mvc; using Microsoft.CodeAnalysis; @@ -42,11 +43,16 @@ var (result, compilation) = await RunGeneratorAsync(source, updatedSource); var outputSteps = GetRunStepOutputs(result); - Assert.All(outputSteps, (value) => Assert.Equal(IncrementalStepRunReason.New, value.Reason)); + Assert.Collection(outputSteps, + // First source output for diagnostics is unchanged. + step => Assert.Equal(IncrementalStepRunReason.Unchanged, step.Reason), + // Second source output for generated code is changed. + step => Assert.Equal(IncrementalStepRunReason.Modified, step.Reason) + ); } [Fact] - public async Task MapAction_ChangeBodyParamNullability_TriggersUpdate() + public async Task MapAction_ChangeBodyParamNullability_TriggersUpdate_ForSourceOnly() { var source = $"""app.MapGet("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)} todo) => TypedResults.Ok(todo));"""; var updatedSource = $""" @@ -58,8 +64,16 @@ var (result, compilation) = await RunGeneratorAsync(source, updatedSource); var outputSteps = GetRunStepOutputs(result); - Assert.All(outputSteps, (value) => Assert.Equal(IncrementalStepRunReason.New, value.Reason)); + Assert.Collection(outputSteps, + // First source output for diagnostics is unchanged. + step => Assert.Equal(IncrementalStepRunReason.Unchanged, step.Reason), + // Second source output for generated code is changed. + step => Assert.Equal(IncrementalStepRunReason.Modified, step.Reason) + ); } - private static IEnumerable<(object Value, IncrementalStepRunReason Reason)> GetRunStepOutputs(GeneratorRunResult? result) => result?.TrackedOutputSteps.SelectMany(step => step.Value).SelectMany(value => value.Outputs); + private static IEnumerable<(object Value, IncrementalStepRunReason Reason)> GetRunStepOutputs(GeneratorRunResult? result) + => result?.TrackedOutputSteps + .SelectMany(step => step.Value) + .SelectMany(value => value.Outputs); } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateCreationTestBase.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateCreationTestBase.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateCreationTestBase.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateCreationTestBase.cs 2023-11-13 13:20:34.000000000 +0000 @@ -34,7 +34,7 @@ protected abstract bool IsGeneratorEnabled { get; } - internal static readonly CSharpParseOptions ParseOptions = new CSharpParseOptions(LanguageVersion.Preview).WithFeatures(new[] { new KeyValuePair("InterceptorsPreview", "") }); + internal static readonly CSharpParseOptions ParseOptions = new CSharpParseOptions(LanguageVersion.Preview).WithFeatures(new[] { new KeyValuePair("InterceptorsPreviewNamespaces", "Microsoft.AspNetCore.Http.Generated") }); private static readonly Project _baseProject = CreateProject(); internal async Task<(GeneratorRunResult?, Compilation)> RunGeneratorAsync(string sources, params string[] updatedSources) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Routing/src/Builder/RoutingEndpointConventionBuilderExtensions.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Routing/src/Builder/RoutingEndpointConventionBuilderExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Routing/src/Builder/RoutingEndpointConventionBuilderExtensions.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Routing/src/Builder/RoutingEndpointConventionBuilderExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -159,7 +159,7 @@ { ArgumentNullException.ThrowIfNull(builder); - builder.WithMetadata(AntiforgeryMetadata.ValidationNotRequired); + builder.Finally(builder => builder.Metadata.Add(AntiforgeryMetadata.ValidationNotRequired)); return builder; } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Routing/test/FunctionalTests/MinimalFormTests.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Routing/test/FunctionalTests/MinimalFormTests.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Routing/test/FunctionalTests/MinimalFormTests.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Routing/test/FunctionalTests/MinimalFormTests.cs 2023-11-13 13:20:34.000000000 +0000 @@ -292,6 +292,53 @@ Assert.Equal(DateTime.Today.AddDays(1), result.DueDate); } + [Fact] + public async Task MapPost_WithForm_WithoutAntiforgery_AndRouteGroup_WithoutMiddleware_Works() + { + using var host = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder + .Configure(app => + { + app.UseRouting(); + app.UseEndpoints(b => + { + var group = b.MapGroup("/todo").DisableAntiforgery(); + group.MapPost("", ([FromForm] Todo todo) => todo); + }); + }) + .UseTestServer(); + }) + .ConfigureServices(services => + { + services.AddRouting(); + services.AddAntiforgery(); + }) + .Build(); + + using var server = host.GetTestServer(); + await host.StartAsync(); + var client = server.CreateClient(); + + var request = new HttpRequestMessage(HttpMethod.Post, "todo"); + var nameValueCollection = new List> + { + new KeyValuePair("name", "Test task"), + new KeyValuePair("isComplete", "false"), + new KeyValuePair("dueDate", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)), + }; + request.Content = new FormUrlEncodedContent(nameValueCollection); + + var response = await client.SendAsync(request); + response.EnsureSuccessStatusCode(); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonSerializer.Deserialize(body, SerializerOptions); + Assert.Equal("Test task", result.Name); + Assert.False(result.IsCompleted); + Assert.Equal(DateTime.Today.AddDays(1), result.DueDate); + } + public static IEnumerable RequestDelegateData { get diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Routing/test/UnitTests/Microsoft.AspNetCore.Routing.Tests.csproj dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Routing/test/UnitTests/Microsoft.AspNetCore.Routing.Tests.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Routing/test/UnitTests/Microsoft.AspNetCore.Routing.Tests.csproj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Routing/test/UnitTests/Microsoft.AspNetCore.Routing.Tests.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -18,7 +18,7 @@ - + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Routing/test/UnitTests/RoutingMetricsTests.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Routing/test/UnitTests/RoutingMetricsTests.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Http/Routing/test/UnitTests/RoutingMetricsTests.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Http/Routing/test/UnitTests/RoutingMetricsTests.cs 2023-11-13 13:20:34.000000000 +0000 @@ -10,10 +10,10 @@ using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -using Microsoft.Extensions.Telemetry.Testing.Metrics; using Moq; namespace Microsoft.AspNetCore.Routing; diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Identity/Core/src/Data/InfoResponse.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Identity/Core/src/Data/InfoResponse.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Identity/Core/src/Data/InfoResponse.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Identity/Core/src/Data/InfoResponse.cs 2023-11-13 13:20:34.000000000 +0000 @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Security.Claims; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; namespace Microsoft.AspNetCore.Identity.Data; @@ -21,9 +19,4 @@ /// Indicates whether or not the has been confirmed yet. /// public required bool IsEmailConfirmed { get; init; } - - /// - /// The from the authenticated . - /// - public required IDictionary Claims { get; init; } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Identity/Core/src/IdentityApiEndpointRouteBuilderExtensions.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Identity/Core/src/IdentityApiEndpointRouteBuilderExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Identity/Core/src/IdentityApiEndpointRouteBuilderExtensions.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Identity/Core/src/IdentityApiEndpointRouteBuilderExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -340,7 +340,7 @@ return TypedResults.NotFound(); } - return TypedResults.Ok(await CreateInfoResponseAsync(user, claimsPrincipal, userManager)); + return TypedResults.Ok(await CreateInfoResponseAsync(user, userManager)); }); accountGroup.MapPost("/info", async Task, ValidationProblem, NotFound>> @@ -382,7 +382,7 @@ } } - return TypedResults.Ok(await CreateInfoResponseAsync(user, claimsPrincipal, userManager)); + return TypedResults.Ok(await CreateInfoResponseAsync(user, userManager)); }); async Task SendConfirmationEmailAsync(TUser user, UserManager userManager, HttpContext context, string email, bool isChange = false) @@ -452,14 +452,13 @@ return TypedResults.ValidationProblem(errorDictionary); } - private static async Task CreateInfoResponseAsync(TUser user, ClaimsPrincipal claimsPrincipal, UserManager userManager) + private static async Task CreateInfoResponseAsync(TUser user, UserManager userManager) where TUser : class { return new() { Email = await userManager.GetEmailAsync(user) ?? throw new NotSupportedException("Users must have an email."), IsEmailConfirmed = await userManager.IsEmailConfirmedAsync(user), - Claims = claimsPrincipal.Claims.ToDictionary(c => c.Type, c => c.Value), }; } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -9,8 +9,6 @@ false true true - - $(Features);InterceptorsPreview $(InterceptorsPreviewNamespaces);Microsoft.AspNetCore.Http.Generated diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Identity/Core/src/PublicAPI.Unshipped.txt dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Identity/Core/src/PublicAPI.Unshipped.txt --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Identity/Core/src/PublicAPI.Unshipped.txt 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Identity/Core/src/PublicAPI.Unshipped.txt 2023-11-13 13:20:34.000000000 +0000 @@ -12,8 +12,6 @@ Microsoft.AspNetCore.Identity.Data.InfoRequest.OldPassword.get -> string? Microsoft.AspNetCore.Identity.Data.InfoRequest.OldPassword.init -> void Microsoft.AspNetCore.Identity.Data.InfoResponse -Microsoft.AspNetCore.Identity.Data.InfoResponse.Claims.get -> System.Collections.Generic.IDictionary! -Microsoft.AspNetCore.Identity.Data.InfoResponse.Claims.init -> void Microsoft.AspNetCore.Identity.Data.InfoResponse.Email.get -> string! Microsoft.AspNetCore.Identity.Data.InfoResponse.Email.init -> void Microsoft.AspNetCore.Identity.Data.InfoResponse.InfoResponse() -> void diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Identity/test/Identity.FunctionalTests/MapIdentityApiTests.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Identity/test/Identity.FunctionalTests/MapIdentityApiTests.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Identity/test/Identity.FunctionalTests/MapIdentityApiTests.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Identity/test/Identity.FunctionalTests/MapIdentityApiTests.cs 2023-11-13 13:20:34.000000000 +0000 @@ -13,7 +13,6 @@ using Identity.DefaultUI.WebSite; using Identity.DefaultUI.WebSite.Data; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Identity.UI.Services; @@ -862,7 +861,7 @@ client.DefaultRequestHeaders.Authorization = new("Bearer", recoveryAccessToken); var updated2faResponse = await client.PostAsJsonAsync("/identity/manage/2fa", new object()); - var updated2faContent = await updated2faResponse.Content.ReadFromJsonAsync();; + var updated2faContent = await updated2faResponse.Content.ReadFromJsonAsync(); Assert.Equal(8, updated2faContent.GetProperty("recoveryCodesLeft").GetInt32()); Assert.Null(updated2faContent.GetProperty("recoveryCodes").GetString()); @@ -1013,25 +1012,6 @@ AssertOk(await client.PostAsJsonAsync("/identity/login", new { Email = confirmedEmail, Password = newPassword })); } - [Fact] - public async Task CanGetClaims() - { - await using var app = await CreateAppAsync(); - using var client = app.GetTestClient(); - - await RegisterAsync(client); - await LoginAsync(client); - - var infoResponse = await client.GetFromJsonAsync("/identity/manage/info"); - Assert.Equal(Email, infoResponse.GetProperty("email").GetString()); - - var claims = infoResponse.GetProperty("claims"); - Assert.Equal(Email, claims.GetProperty(ClaimTypes.Name).GetString()); - Assert.Equal(Email, claims.GetProperty(ClaimTypes.Email).GetString()); - Assert.Equal("pwd", claims.GetProperty("amr").GetString()); - Assert.NotNull(claims.GetProperty(ClaimTypes.NameIdentifier).GetString()); - } - [Theory] [MemberData(nameof(AddIdentityModes))] public async Task CanChangeEmail(string addIdentityModes) @@ -1058,12 +1038,12 @@ Assert.Equal(Email, infoResponse.GetProperty("email").GetString()); Assert.True(infoResponse.GetProperty("isEmailConfirmed").GetBoolean()); - var infoClaims = infoResponse.GetProperty("claims"); - Assert.Equal("pwd", infoClaims.GetProperty("amr").GetString()); - Assert.Equal(Email, infoClaims.GetProperty(ClaimTypes.Name).GetString()); - Assert.Equal(Email, infoClaims.GetProperty(ClaimTypes.Email).GetString()); + var infoClaims = await client.GetFromJsonAsync("/auth/claims"); + Assert.Equal("pwd", GetSingleClaim(infoClaims, "amr")); + Assert.Equal(Email, GetSingleClaim(infoClaims, ClaimTypes.Name)); + Assert.Equal(Email, GetSingleClaim(infoClaims, ClaimTypes.Email)); - var originalNameIdentifier = infoResponse.GetProperty("claims").GetProperty(ClaimTypes.NameIdentifier).GetString(); + var originalNameIdentifier = GetSingleClaim(infoClaims, ClaimTypes.NameIdentifier); var newEmail = $"New-{Email}"; // The email must pass DataAnnotations validation by EmailAddressAttribute. @@ -1077,10 +1057,10 @@ Assert.True(infoPostContent.GetProperty("isEmailConfirmed").GetBoolean()); // And none of the claims have yet been updated. - var infoPostClaims = infoPostContent.GetProperty("claims"); - Assert.Equal(Email, infoPostClaims.GetProperty(ClaimTypes.Name).GetString()); - Assert.Equal(Email, infoPostClaims.GetProperty(ClaimTypes.Email).GetString()); - Assert.Equal(originalNameIdentifier, infoClaims.GetProperty(ClaimTypes.NameIdentifier).GetString()); + var infoPostClaims = await client.GetFromJsonAsync("/auth/claims"); + Assert.Equal(Email, GetSingleClaim(infoPostClaims, ClaimTypes.Name)); + Assert.Equal(Email, GetSingleClaim(infoPostClaims, ClaimTypes.Email)); + Assert.Equal(originalNameIdentifier, GetSingleClaim(infoPostClaims, ClaimTypes.NameIdentifier)); // We cannot log in with the new email until we confirm the email change. await AssertProblemAsync(await client.PostAsJsonAsync("/identity/login", new { Email = newEmail, Password }), @@ -1103,10 +1083,10 @@ Assert.Equal(newEmail, infoAfterEmailChange.GetProperty("email").GetString()); // The email still won't be available as a claim until we get a new token. - var claimsAfterEmailChange = infoAfterEmailChange.GetProperty("claims"); - Assert.Equal(Email, claimsAfterEmailChange.GetProperty(ClaimTypes.Name).GetString()); - Assert.Equal(Email, claimsAfterEmailChange.GetProperty(ClaimTypes.Email).GetString()); - Assert.Equal(originalNameIdentifier, infoClaims.GetProperty(ClaimTypes.NameIdentifier).GetString()); + var claimsAfterEmailChange = await client.GetFromJsonAsync("/auth/claims"); + Assert.Equal(Email, GetSingleClaim(claimsAfterEmailChange, ClaimTypes.Name)); + Assert.Equal(Email, GetSingleClaim(claimsAfterEmailChange, ClaimTypes.Email)); + Assert.Equal(originalNameIdentifier, GetSingleClaim(claimsAfterEmailChange, ClaimTypes.NameIdentifier)); // And now the email has changed, the refresh token is invalidated by the security stamp. AssertUnauthorizedAndEmpty(await client.PostAsJsonAsync("/identity/refresh", new { RefreshToken = originalRefreshToken })); @@ -1118,10 +1098,10 @@ Assert.Equal(newEmail, infoAfterFinalLogin.GetProperty("email").GetString()); Assert.True(infoAfterFinalLogin.GetProperty("isEmailConfirmed").GetBoolean()); - var claimsAfterFinalLogin = infoAfterFinalLogin.GetProperty("claims"); - Assert.Equal(newEmail, claimsAfterFinalLogin.GetProperty(ClaimTypes.Name).GetString()); - Assert.Equal(newEmail, claimsAfterFinalLogin.GetProperty(ClaimTypes.Email).GetString()); - Assert.Equal(originalNameIdentifier, infoClaims.GetProperty(ClaimTypes.NameIdentifier).GetString()); + var claimsAfterFinalLogin = await client.GetFromJsonAsync("/auth/claims"); + Assert.Equal(newEmail, GetSingleClaim(claimsAfterFinalLogin, ClaimTypes.Name)); + Assert.Equal(newEmail, GetSingleClaim(claimsAfterFinalLogin, ClaimTypes.Email)); + Assert.Equal(originalNameIdentifier, GetSingleClaim(claimsAfterFinalLogin, ClaimTypes.NameIdentifier)); } [Fact] @@ -1152,12 +1132,13 @@ var infoResponse = await client.GetFromJsonAsync("/identity/manage/info"); Assert.Equal(Email, infoResponse.GetProperty("email").GetString()); - var infoClaims = infoResponse.GetProperty("claims"); - Assert.Equal("pwd", infoClaims.GetProperty("amr").GetString()); - Assert.Equal(Email, infoClaims.GetProperty(ClaimTypes.Name).GetString()); - Assert.Equal(Email, infoClaims.GetProperty(ClaimTypes.Email).GetString()); - var originalNameIdentifier = infoResponse.GetProperty("claims").GetProperty(ClaimTypes.NameIdentifier).GetString(); + var infoClaims = await client.GetFromJsonAsync("/auth/claims"); + Assert.Equal("pwd", GetSingleClaim(infoClaims, "amr")); + Assert.Equal(Email, GetSingleClaim(infoClaims, ClaimTypes.Name)); + Assert.Equal(Email, GetSingleClaim(infoClaims, ClaimTypes.Email)); + + var originalNameIdentifier = GetSingleClaim(infoClaims, ClaimTypes.NameIdentifier); var newEmail = $"NewEmailPrefix-{Email}"; var infoPostResponse = await client.PostAsJsonAsync("/identity/manage/info", new { newEmail }); @@ -1169,9 +1150,9 @@ Assert.Equal(Email, infoPostContent.GetProperty("email").GetString()); // The claims have not been updated to match. - var infoPostClaims = infoPostContent.GetProperty("claims"); - Assert.Equal(Email, infoPostClaims.GetProperty(ClaimTypes.Email).GetString()); - Assert.Equal(originalNameIdentifier, infoClaims.GetProperty(ClaimTypes.NameIdentifier).GetString()); + var infoPostClaims = await client.GetFromJsonAsync("/auth/claims"); + Assert.Equal(Email, GetSingleClaim(infoPostClaims, ClaimTypes.Email)); + Assert.Equal(originalNameIdentifier, GetSingleClaim(infoPostClaims, ClaimTypes.NameIdentifier)); // Two emails have now been sent. The first was sent during registration. And the second for the email change. Assert.Equal(2, emailSender.Emails.Count); @@ -1191,9 +1172,9 @@ Assert.Equal(newEmail, infoAfterEmailChange.GetProperty("email").GetString()); // The email still won't be available as a claim until we get a new cookie. - var claimsAfterEmailChange = infoAfterEmailChange.GetProperty("claims"); - Assert.Equal(Email, claimsAfterEmailChange.GetProperty(ClaimTypes.Email).GetString()); - Assert.Equal(originalNameIdentifier, infoClaims.GetProperty(ClaimTypes.NameIdentifier).GetString()); + var claimsAfterEmailChange = await client.GetFromJsonAsync("/auth/claims"); + Assert.Equal(Email, GetSingleClaim(claimsAfterEmailChange, ClaimTypes.Email)); + Assert.Equal(originalNameIdentifier, GetSingleClaim(claimsAfterEmailChange, ClaimTypes.NameIdentifier)); // We will finally see all the claims updated after logging in again. var secondLoginResponse = await client.PostAsJsonAsync("/identity/login?useCookies=true", new { Email = newEmail, Password }); @@ -1202,10 +1183,10 @@ var infoAfterFinalLogin = await client.GetFromJsonAsync("/identity/manage/info"); Assert.Equal(newEmail, infoAfterFinalLogin.GetProperty("email").GetString()); - var claimsAfterFinalLogin = infoAfterFinalLogin.GetProperty("claims"); - Assert.Equal(newEmail, claimsAfterFinalLogin.GetProperty(ClaimTypes.Name).GetString()); - Assert.Equal(newEmail, claimsAfterFinalLogin.GetProperty(ClaimTypes.Email).GetString()); - Assert.Equal(originalNameIdentifier, infoClaims.GetProperty(ClaimTypes.NameIdentifier).GetString()); + var claimsAfterFinalLogin = await client.GetFromJsonAsync("/auth/claims"); + Assert.Equal(newEmail, GetSingleClaim(claimsAfterFinalLogin, ClaimTypes.Name)); + Assert.Equal(newEmail, GetSingleClaim(claimsAfterFinalLogin, ClaimTypes.Email)); + Assert.Equal(originalNameIdentifier, GetSingleClaim(claimsAfterFinalLogin, ClaimTypes.NameIdentifier)); } [Fact] @@ -1321,6 +1302,8 @@ authGroup.MapGet("/hello", (ClaimsPrincipal user) => $"Hello, {user.Identity?.Name}!"); + authGroup.MapGet("/claims", (ClaimsPrincipal user) => user.Claims.Select(c => new { c.Type, c.Value })); + await dbConnection.OpenAsync(); await app.Services.GetRequiredService().Database.EnsureCreatedAsync(); @@ -1367,6 +1350,9 @@ public static object[][] AddIdentityModes => AddIdentityActions.Keys.Select(key => new object[] { key }).ToArray(); + private static string? GetSingleClaim(JsonElement claims, string name) + => claims.EnumerateArray().Single(e => e.GetProperty("type").GetString() == name).GetProperty("value").GetString(); + private static string GetEmailConfirmationLink(TestEmail email) { // Update if we add more links to the email. diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/Diagnostics/src/DiagnosticsMetrics.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/Diagnostics/src/DiagnosticsMetrics.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/Diagnostics/src/DiagnosticsMetrics.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/Diagnostics/src/DiagnosticsMetrics.cs 2023-11-13 13:20:34.000000000 +0000 @@ -39,7 +39,7 @@ private void RequestExceptionCore(string exceptionName, ExceptionResult result, string? handler) { var tags = new TagList(); - tags.Add("exception.type", exceptionName); + tags.Add("error.type", exceptionName); tags.Add("aspnetcore.diagnostics.exception.result", GetExceptionResult(result)); if (handler != null) { diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/Diagnostics/src/DiagnosticsTelemetry.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/Diagnostics/src/DiagnosticsTelemetry.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/Diagnostics/src/DiagnosticsTelemetry.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/Diagnostics/src/DiagnosticsTelemetry.cs 2023-11-13 13:20:34.000000000 +0000 @@ -15,7 +15,7 @@ if (context.Features.Get() is { } tagsFeature) { - tagsFeature.Tags.Add(new KeyValuePair("exception.type", ex.GetType().FullName)); + tagsFeature.Tags.Add(new KeyValuePair("error.type", ex.GetType().FullName)); } } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -15,9 +15,8 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Diagnostics.Metrics; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Telemetry.Testing.Metrics; namespace Microsoft.AspNetCore.Diagnostics; @@ -580,7 +579,7 @@ { Assert.True(m.Value > 0); Assert.Equal(500, (int)m.Tags["http.response.status_code"]); - Assert.Equal("System.Exception", (string)m.Tags["exception.type"]); + Assert.Equal("System.Exception", (string)m.Tags["error.type"]); }); Assert.Collection(requestExceptionCollector.GetMeasurementSnapshot(), m => AssertRequestException(m, "System.Exception", "unhandled")); @@ -589,7 +588,7 @@ private static void AssertRequestException(CollectedMeasurement measurement, string exceptionName, string result, string handler = null) { Assert.Equal(1, measurement.Value); - Assert.Equal(exceptionName, (string)measurement.Tags["exception.type"]); + Assert.Equal(exceptionName, (string)measurement.Tags["error.type"]); Assert.Equal(result, measurement.Tags["aspnetcore.diagnostics.exception.result"].ToString()); if (handler == null) { diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerMiddlewareTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerMiddlewareTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerMiddlewareTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerMiddlewareTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -19,7 +19,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -using Microsoft.Extensions.Telemetry.Testing.Metrics; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; using Moq; namespace Microsoft.AspNetCore.Diagnostics; @@ -318,7 +318,7 @@ private static void AssertRequestException(CollectedMeasurement measurement, string exceptionName, string result, string handler = null) { Assert.Equal(1, measurement.Value); - Assert.Equal(exceptionName, (string)measurement.Tags["exception.type"]); + Assert.Equal(exceptionName, (string)measurement.Tags["error.type"]); Assert.Equal(result, measurement.Tags["aspnetcore.diagnostics.exception.result"].ToString()); if (handler == null) { diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -14,7 +14,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; -using Microsoft.Extensions.Telemetry.Testing.Metrics; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; namespace Microsoft.AspNetCore.Diagnostics; @@ -962,7 +962,7 @@ { Assert.True(m.Value > 0); Assert.Equal(404, (int)m.Tags["http.response.status_code"]); - Assert.Equal("System.Exception", (string)m.Tags["exception.type"]); + Assert.Equal("System.Exception", (string)m.Tags["error.type"]); }); } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/Microsoft.AspNetCore.Diagnostics.Tests.csproj dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/Microsoft.AspNetCore.Diagnostics.Tests.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/Microsoft.AspNetCore.Diagnostics.Tests.csproj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/Diagnostics/test/UnitTests/Microsoft.AspNetCore.Diagnostics.Tests.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -23,7 +23,7 @@ - + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/OutputCaching/src/OutputCacheEntry.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/OutputCaching/src/OutputCacheEntry.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/OutputCaching/src/OutputCacheEntry.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/OutputCaching/src/OutputCacheEntry.cs 2023-11-13 13:20:34.000000000 +0000 @@ -3,7 +3,6 @@ using System.Buffers; using System.IO.Pipelines; -using System.Runtime.InteropServices; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; @@ -18,8 +17,6 @@ StatusCode = statusCode; } - private bool _recycleBuffers; // does this instance own the memory behind the segments? - public StringValues FindHeader(string key) { TryFindHeader(key, out var value); @@ -69,26 +66,9 @@ internal void SetBody(ReadOnlySequence value, bool recycleBuffers) { Body = value; - _recycleBuffers = recycleBuffers; - } - - public void Dispose() - { - var headers = Headers; - var body = Body; - Headers = default; - Body = default; - Recycle(headers); - RecyclableReadOnlySequenceSegment.RecycleChain(body, _recycleBuffers); - // ^^ note that this only recycles the chain, not the actual buffers - } - - private static void Recycle(ReadOnlyMemory value) - { - if (MemoryMarshal.TryGetArray(value, out var segment) && segment.Array is { Length: > 0 }) - { - ArrayPool.Shared.Return(segment.Array); - } + _ = recycleBuffers; // satisfy IDE0060 + // note that recycleBuffers is not stored currently, until OutputCacheEntry buffer recycling is re-implemented; + // it indicates whether this instance "owns" the memory behind the segments, such that they can be recycled later if desired } internal OutputCacheEntry CreateBodyFrom(IList segments) // mainly used from tests @@ -118,6 +98,7 @@ if (index == 0) // only ignored headers { ArrayPool<(string, StringValues)>.Shared.Return(arr); + Headers = default; } else { @@ -142,4 +123,6 @@ public ValueTask CopyToAsync(PipeWriter destination, CancellationToken cancellationToken) => RecyclableReadOnlySequenceSegment.CopyToAsync(Body, destination, cancellationToken); + + public void Dispose() { } // intention here is to add recycling; this was removed late in NET8, but is retained as a callback } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/OutputCaching/src/OutputCacheMiddleware.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/OutputCaching/src/OutputCacheMiddleware.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/OutputCaching/src/OutputCacheMiddleware.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/OutputCaching/src/OutputCacheMiddleware.cs 2023-11-13 13:20:34.000000000 +0000 @@ -200,6 +200,7 @@ { // avoid recycling in unknown outcomes, especially re concurrent buffer access thru cancellation hasException = true; + throw; } finally { diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/OutputCaching/src/RecyclableReadOnlySequenceSegment.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/OutputCaching/src/RecyclableReadOnlySequenceSegment.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/OutputCaching/src/RecyclableReadOnlySequenceSegment.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/OutputCaching/src/RecyclableReadOnlySequenceSegment.cs 2023-11-13 13:20:34.000000000 +0000 @@ -4,17 +4,25 @@ using System.Buffers; using System.Collections.Concurrent; using System.IO.Pipelines; -using System.Runtime.InteropServices; namespace Microsoft.AspNetCore.OutputCaching; +// TODO: reinstate pooling +// context: a last-minute bug was detected during net8 preparation that impacted +// buffer reuse from output-cache (the only consumer of this type); the preferred +// solution for this is understood, but is more "moving parts" than we're comfortable +// touching in the last phase of net8, so to avoid risk, *temporarily*, the buffer +// reuse is disabled; this is consistent with net7, which never used buffer recycling +// in output-cache, so this is not a regression. The work to properly implement buffer +// reuse in output-cache is in progress to be merged in net9 and hopefully backported +// into a net8 service release. internal sealed class RecyclableReadOnlySequenceSegment : ReadOnlySequenceSegment { public int Length => Memory.Length; private RecyclableReadOnlySequenceSegment() { } public static RecyclableReadOnlySequenceSegment Create(int minimumLength, RecyclableReadOnlySequenceSegment? previous) - => Create(Rent(minimumLength), previous); + => Create(GetBuffer(minimumLength), previous); public static RecyclableReadOnlySequenceSegment Create(ReadOnlyMemory memory, RecyclableReadOnlySequenceSegment? previous) { @@ -112,14 +120,16 @@ } } - private static byte[] Rent(int minimumLength) - => ArrayPool.Shared.Rent(minimumLength); - - private static void Recycle(ReadOnlyMemory value) - { - if (MemoryMarshal.TryGetArray(value, out var segment) && segment.Offset == 0 && segment.Count != 0) - { - ArrayPool.Shared.Return(segment.Array!); - } + // TODO: reinstate ArrayPool.Shared usage.Rent(minimumLength); + private static byte[] GetBuffer(int minimumLength) + => new byte[minimumLength]; + + private static void Recycle(ReadOnlyMemory _) + { + // TODO: reinstate buffer recycling + //if (MemoryMarshal.TryGetArray(value, out var segment) && segment.Offset == 0 && segment.Count != 0) + //{ + // ArrayPool.Shared.Return(segment.Array!); + //} } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/OutputCaching/test/OutputCacheTests.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/OutputCaching/test/OutputCacheTests.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/OutputCaching/test/OutputCacheTests.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/OutputCaching/test/OutputCacheTests.cs 2023-11-13 13:20:34.000000000 +0000 @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Net; using System.Net.Http; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; @@ -935,6 +936,100 @@ } } + [Fact] + public async Task MiddlewareFaultsAreObserved() + { + var builders = TestUtils.CreateBuildersWithOutputCaching(contextAction: _ => throw new SomeException()); + + foreach (var builder in builders) + { + using var host = builder.Build(); + + await host.StartAsync(); + + using var server = host.GetTestServer(); + + for (int i = 0; i < 10; i++) + { + await RunClient(server); + } + } + + static async Task RunClient(TestServer server) + { + var client = server.CreateClient(); + await Assert.ThrowsAsync( + () => client.SendAsync(new HttpRequestMessage(HttpMethod.Get, ""))); + } + } + + sealed class SomeException : Exception { } + + [Fact] + public async Task ServesCorrectlyUnderConcurrentLoad() + { + var builders = TestUtils.CreateBuildersWithOutputCaching(); + + foreach (var builder in builders) + { + using var host = builder.Build(); + + await host.StartAsync(); + + using var server = host.GetTestServer(); + + var guid = await RunClient(server, -1); + + var clients = new Task[1024]; + for (int i = 0; i < clients.Length; i++) + { + clients[i] = Task.Run(() => RunClient(server, i)); + } + await Task.WhenAll(clients); + + // note already completed + for (int i = 0; i < clients.Length; i++) + { + Assert.Equal(guid, await clients[i]); + } + } + + static async Task RunClient(TestServer server, int id) + { + string s = null; + try + { + var client = server.CreateClient(); + var resp = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "")); + var len = resp.Content.Headers.ContentLength; + s = await resp.Content.ReadAsStringAsync(); + + Assert.NotNull(len); + Guid value; + switch (len.Value) + { + case 36: + // usually we just write a guid + Assert.True(Guid.TryParse(s, out value)); + break; + case 98: + // the file-based builder prepends extra data + Assert.True(Guid.TryParse(s.Substring(s.Length - 36), out value)); + break; + default: + Assert.Fail($"Unexpected length: {len.Value}"); + value = Guid.NewGuid(); // not reached + break; + } + return value; + } + catch (Exception ex) + { + throw new InvalidOperationException($"Client {id} failed; payload '{s}', failure: {ex.Message}", ex); + } + } + } + private static void Assert304Headers(HttpResponseMessage initialResponse, HttpResponseMessage subsequentResponse) { // https://tools.ietf.org/html/rfc7232#section-4.1 diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/RateLimiting/test/Microsoft.AspNetCore.RateLimiting.Tests.csproj dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/RateLimiting/test/Microsoft.AspNetCore.RateLimiting.Tests.csproj --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/RateLimiting/test/Microsoft.AspNetCore.RateLimiting.Tests.csproj 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/RateLimiting/test/Microsoft.AspNetCore.RateLimiting.Tests.csproj 2023-11-13 13:20:34.000000000 +0000 @@ -10,7 +10,7 @@ - + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/RateLimiting/test/RateLimitingMetricsTests.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/RateLimiting/test/RateLimitingMetricsTests.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Middleware/RateLimiting/test/RateLimitingMetricsTests.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Middleware/RateLimiting/test/RateLimitingMetricsTests.cs 2023-11-13 13:20:34.000000000 +0000 @@ -11,10 +11,10 @@ using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.Metrics; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -using Microsoft.Extensions.Telemetry.Testing.Metrics; using Moq; namespace Microsoft.AspNetCore.RateLimiting; diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Mvc/Mvc/test/MvcServiceCollectionExtensionsTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Mvc/Mvc/test/MvcServiceCollectionExtensionsTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/Mvc/Mvc/test/MvcServiceCollectionExtensionsTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/Mvc/Mvc/test/MvcServiceCollectionExtensionsTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -26,6 +26,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.Metrics; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Moq; @@ -676,6 +677,9 @@ .Setup(e => e.ApplicationName) .Returns(typeof(MvcServiceCollectionExtensionsTest).Assembly.GetName().Name); + environment.Setup(e => e.WebRootFileProvider) + .Returns(new NullFileProvider()); + return environment.Object; } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/OpenApi/src/OpenApiGenerator.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/OpenApi/src/OpenApiGenerator.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/OpenApi/src/OpenApiGenerator.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/OpenApi/src/OpenApiGenerator.cs 2023-11-13 13:20:34.000000000 +0000 @@ -188,7 +188,7 @@ if (eligibileAnnotations.Count == 0) { - GenerateDefaultResponses(eligibileAnnotations, responseType); + GenerateDefaultResponses(eligibileAnnotations, responseType!); } foreach (var annotation in eligibileAnnotations) diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/README-BASELINES.md dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/README-BASELINES.md --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/README-BASELINES.md 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/README-BASELINES.md 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,19 @@ +# Generating template-baselines.json + +For small project template changes, you may be able to edit the `template-baselines.json` file manually. This is a good way to ensure you have correct expectations about the effects of your changes. + +For larger changes such as adding entirely new templates, it may be impractical to type out the changes to `template-baselines.json` manually. In those cases you can follow a procedure like the following. + + 1. Ensure you've configured the necessary environment variables: + - `set PATH=c:\git\dotnet\aspnetcore\.dotnet\;%PATH%` (update path as needed) + - `set DOTNET_ROOT=c:\git\dotnet\aspnetcore\.dotnet` (update path as needed) + 2. Get to a position where you can execute the modified template(s) locally, i.e.: + - Use `dotnet pack ProjectTemplatesNoDeps.slnf` (possibly with `--no-restore --no-dependencies`) to regenerate `Microsoft.DotNet.Web.ProjectTemplates.*.nupkg` + - Run one of the `scripts/*.ps1` scripts to install your template pack and execute your chosen template. For example, run `powershell .\scripts\Run-BlazorWeb-Locally.ps1` + - Once that has run, you should see your updated template listed when you execute `dotnet new list` or `dotnet new YourTemplateName --help`. At the point you can run `dotnet new YourTemplateName -o SomePath` directly if you want. However each time you edit template sources further, you will need to run `dotnet new uninstall Microsoft.DotNet.Web.ProjectTemplates.8.0` and then go back to the start of this whole step. + - Tip: the following command combines the above steps, to go directly from editing template sources to an updated local project output: `dotnet pack ProjectTemplatesNoDeps.slnf --no-restore --no-dependencies && dotnet new uninstall Microsoft.DotNet.Web.ProjectTemplates.8.0 && rm -rf scripts\MyBlazorApp && powershell .\scripts\Run-BlazorWeb-Locally.ps1` + 3. After generating a particular project's output, the following can be run in a Bash prompt (e.g., using WSL): + - `cd src/ProjectTemplates/scripts` + - `export PROJECT_NAME=MyBlazorApp` (update as necessary - note this is the name of the directly under `scripts` containing your project output) + - `find $PROJECT_NAME -type f -not -path "*/obj/*" -not -path "*/bin/*" -not -path "*/.publish/*" | sed -e "s/^$PROJECT_NAME\///" | sed -e "s/$PROJECT_NAME/{ProjectName}/g" | sed 's/.*/ "&",/' | sort -f` + - This will emit the JSON-formatted lines you can manually insert into the relevant place inside `template-baselines.json` diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/README.md dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/README.md --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/README.md 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/README.md 2023-11-13 13:20:34.000000000 +0000 @@ -50,7 +50,7 @@ Then, use one of: -1. Run `src\ProjectTemplates\build.cmd -test -NoRestore -NoBuild -NoBuilddeps -configuration Release` (or equivalent src\ProjectTemplates\build.sh` command) to run all template tests. +1. Run `src\ProjectTemplates\build.cmd -test -NoRestore -NoBuild -NoBuildDeps -configuration Release` (or equivalent src\ProjectTemplates\build.sh` command) to run all template tests. 1. To test specific templates, use the `Run-[Template]-Locally.ps1` scripts in the script folder. - These scripts do `dotnet new -i` with your packages, but also apply a series of fixes and tweaks to the created template which keep the fact that you don't have a production `Microsoft.AspNetCore.App` from interfering. 1. Run templates manually with `custom-hive` and `disable-sdk-templates` to install to a custom location and turn off the built-in templates e.g. diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Shared/ArgConstants.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Shared/ArgConstants.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Shared/ArgConstants.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Shared/ArgConstants.cs 2023-11-13 13:20:34.000000000 +0000 @@ -14,7 +14,6 @@ public const string CalledApiScopes = "--called-api-scopes"; public const string CalledApiScopesUserReadWrite = $"{CalledApiScopes} user.readwrite"; public const string NoOpenApi = "--no-openapi"; - public const string Auth = "-au"; public const string ClientId = "--client-id"; public const string Domain = "--domain"; public const string DefaultScope = "--default-scope"; @@ -26,5 +25,9 @@ public const string NoHttps = "--no-https"; public const string PublishNativeAot = "--aot"; public const string NoInteractivity = "--interactivity none"; + public const string WebAssemblyInteractivity = "--interactivity WebAssembly"; + public const string AutoInteractivity = "--interactivity Auto"; + public const string GlobalInteractivity = "--all-interactive"; public const string Empty = "--empty"; + public const string IndividualAuth = "--auth Individual"; } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Shared/AspNetProcess.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Shared/AspNetProcess.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Shared/AspNetProcess.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Shared/AspNetProcess.cs 2023-11-13 13:20:34.000000000 +0000 @@ -43,9 +43,6 @@ _output = output; _httpClient = new HttpClient(new HttpClientHandler() { - AllowAutoRedirect = true, - UseCookies = true, - CookieContainer = new CookieContainer(), ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => (certificate.Subject != "CN=localhost" && errors == SslPolicyErrors.None) || certificate?.Thumbprint == _developmentCertificate.CertificateThumbprint, }) { @@ -124,6 +121,14 @@ } } + public async Task AssertPagesNotFound(IEnumerable urls) + { + foreach (var url in urls) + { + await AssertNotFound(url); + } + } + public async Task ContainsLinks(Page page) { var response = await RetryHelper.RetryRequest(async () => @@ -290,8 +295,10 @@ } } -public class Page +public class Page(string url) { - public string Url { get; set; } + public Page() : this(null) { } + + public string Url { get; set; } = url; public IEnumerable Links { get; set; } } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWasmTemplateTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWasmTemplateTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWasmTemplateTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWasmTemplateTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.CommandLineUtils; -using Newtonsoft.Json.Linq; using Microsoft.Playwright; using Templates.Test.Helpers; @@ -23,7 +22,8 @@ public override string ProjectType { get; } = "blazorwasm"; - [Theory(Skip="https://github.com/dotnet/aspnetcore/issues/47225")] + [Theory] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/47225")] [InlineData(BrowserKind.Chromium)] public async Task BlazorWasmStandaloneTemplate_Works(BrowserKind browserKind) { @@ -316,7 +316,7 @@ // Initially displays the home page await page.WaitForSelectorAsync("h1 >> text=Hello, world!"); - Assert.Equal("Index", (await page.TitleAsync()).Trim()); + Assert.Equal("Home", (await page.TitleAsync()).Trim()); // Can navigate to the counter page await Task.WhenAll( @@ -372,11 +372,8 @@ if (!skipFetchData) { - // Can navigate to the 'fetch data' page - await Task.WhenAll( - page.WaitForNavigationAsync(new() { UrlString = "**/fetchdata" }), - page.WaitForSelectorAsync("h1 >> text=Weather forecast"), - page.ClickAsync("text=Fetch data")); + await page.ClickAsync("a[href=weather]"); + await page.WaitForSelectorAsync("h1 >> text=Weather"); // Asynchronously loads and displays the table of weather forecasts await page.WaitForSelectorAsync("table>tbody>tr"); diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWebTemplateTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWebTemplateTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWebTemplateTest.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWebTemplateTest.cs 1970-01-01 00:00:00.000000000 +0000 @@ -1,126 +0,0 @@ -// 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.Testing; -using Templates.Test.Helpers; -using Xunit.Abstractions; - -namespace Templates.Blazor.Tests; -public class BlazorWebTemplateTest : LoggedTest -{ - public BlazorWebTemplateTest(ProjectFactoryFixture projectFactory) - { - ProjectFactory = projectFactory; - } - - public ProjectFactoryFixture ProjectFactory { get; set; } - - private ITestOutputHelper _output; - public ITestOutputHelper Output - { - get - { - if (_output == null) - { - _output = new TestOutputLogger(Logger); - } - return _output; - } - } - - [ConditionalTheory] - [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - [MemberData(nameof(ArgsData))] - public async Task BlazorWebTemplate_NoAuth(string[] args) - { - var project = await ProjectFactory.CreateProject(Output); - - await project.RunDotNetNewAsync("blazor", args: args); - - var expectedLaunchProfileNames = args.Contains(ArgConstants.NoHttps) - ? new[] { "http", "IIS Express" } - : new[] { "http", "https", "IIS Express" }; - await project.VerifyLaunchSettings(expectedLaunchProfileNames); - - var projectFileContents = ReadFile(project.TemplateOutputDir, $"{project.ProjectName}.csproj"); - Assert.DoesNotContain(".db", projectFileContents); - Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools", projectFileContents); - Assert.DoesNotContain("Microsoft.VisualStudio.Web.CodeGeneration.Design", projectFileContents); - Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools.DotNet", projectFileContents); - Assert.DoesNotContain("Microsoft.Extensions.SecretManager.Tools", projectFileContents); - - await project.RunDotNetPublishAsync(); - - // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build - // later, while the opposite is not true. - - await project.RunDotNetBuildAsync(); - - var pages = new List - { - new Page - { - Url = BlazorTemplatePages.Index, - }, - new Page - { - Url = BlazorTemplatePages.Weather, - } - }; - - if (!args.Contains(ArgConstants.NoInteractivity)) - { - pages.Add(new Page - { - Url = BlazorTemplatePages.Counter, - }); - } - - using (var aspNetProcess = project.StartBuiltProjectAsync()) - { - Assert.False( - aspNetProcess.Process.HasExited, - ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", project, aspNetProcess.Process)); - - await aspNetProcess.AssertPagesOk(pages); - } - - using (var aspNetProcess = project.StartPublishedProjectAsync()) - { - Assert.False( - aspNetProcess.Process.HasExited, - ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", project, aspNetProcess.Process)); - - await aspNetProcess.AssertPagesOk(pages); - } - } - - public static TheoryData ArgsData() => new TheoryData - { - new string[0], - new[] { ArgConstants.UseProgramMain }, - new[] { ArgConstants.NoHttps }, - new[] { ArgConstants.UseProgramMain, ArgConstants.NoHttps }, - new[] { ArgConstants.NoInteractivity }, - new[] { ArgConstants.NoInteractivity, ArgConstants.UseProgramMain }, - new[] { ArgConstants.NoInteractivity, ArgConstants.NoHttps }, - new[] { ArgConstants.NoInteractivity, ArgConstants.UseProgramMain, ArgConstants.NoHttps } - }; - - private string ReadFile(string basePath, string path) - { - var fullPath = Path.Combine(basePath, path); - var doesExist = File.Exists(fullPath); - - Assert.True(doesExist, $"Expected file to exist, but it doesn't: {path}"); - return File.ReadAllText(Path.Combine(basePath, path)); - } - - private class BlazorTemplatePages - { - internal static readonly string Index = ""; - internal static readonly string Weather = "weather"; - internal static readonly string Counter = "counter"; - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/test/Templates.Blazor.Tests/PlaywrightFixture.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/test/Templates.Blazor.Tests/PlaywrightFixture.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/test/Templates.Blazor.Tests/PlaywrightFixture.cs 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/test/Templates.Blazor.Tests/PlaywrightFixture.cs 1970-01-01 00:00:00.000000000 +0000 @@ -1,81 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Microsoft.AspNetCore.BrowserTesting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging.Testing; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace ProjectTemplates.Tests.Infrastructure; - -public class PlaywrightFixture : IAsyncLifetime -{ - private static readonly bool _isCIEnvironment = - !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("ContinuousIntegrationBuild")) || - !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("Helix")); - - private readonly IMessageSink _diagnosticsMessageSink; - private static readonly BrowserManagerConfiguration _config = new BrowserManagerConfiguration(CreateConfiguration(typeof(TTestAssemblyType).Assembly)); - - public PlaywrightFixture(IMessageSink diagnosticsMessageSink) - { - _diagnosticsMessageSink = diagnosticsMessageSink; - } - - private static IConfiguration CreateConfiguration(Assembly assembly) - { - var basePath = Path.GetDirectoryName(assembly.Location); - var os = Environment.OSVersion.Platform switch - { - PlatformID.Win32NT => "win", - PlatformID.Unix => "linux", - PlatformID.MacOSX => "osx", - _ => null - }; - - var builder = new ConfigurationBuilder() - .AddJsonFile(Path.Combine(basePath, "playwrightSettings.json")) - .AddJsonFile(Path.Combine(basePath, $"playwrightSettings.{os}.json"), optional: true); - - if (_isCIEnvironment) - { - builder.AddJsonFile(Path.Combine(basePath, "playwrightSettings.ci.json"), optional: true) - .AddJsonFile(Path.Combine(basePath, $"playwrightSettings.ci.{os}.json"), optional: true); - } - - if (Debugger.IsAttached) - { - builder.AddJsonFile(Path.Combine(basePath, "playwrightSettings.debug.json"), optional: true); - } - - return builder.Build(); - } - - public async Task InitializeAsync() - { - var sink = new TestSink(); - sink.MessageLogged += LogBrowserManagerMessage; - var factory = new TestLoggerFactory(sink, enabled: true); - BrowserManager = await BrowserManager.CreateAsync(_config, factory); - } - - private void LogBrowserManagerMessage(WriteContext context) - { - _diagnosticsMessageSink.OnMessage(new DiagnosticMessage(context.Message)); - } - - public async Task DisposeAsync() - { - await BrowserManager.DisposeAsync(); - } - - public BrowserManager BrowserManager { get; set; } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/test/Templates.Mvc.Tests/BlazorTemplateTest.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/test/Templates.Mvc.Tests/BlazorTemplateTest.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/test/Templates.Mvc.Tests/BlazorTemplateTest.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/test/Templates.Mvc.Tests/BlazorTemplateTest.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,212 @@ +// 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.Testing; +using Templates.Test.Helpers; +using Xunit.Sdk; + +namespace Templates.Mvc.Test; + +public class BlazorTemplateTest : LoggedTest +{ + public BlazorTemplateTest(ProjectFactoryFixture projectFactory) + { + ProjectFactory = projectFactory; + } + + public ProjectFactoryFixture ProjectFactory { get; set; } + + public static TheoryData ArgsData() => + [ + [], + [ArgConstants.UseProgramMain], + [ArgConstants.NoHttps], + [ArgConstants.Empty], + [ArgConstants.NoInteractivity], + [ArgConstants.WebAssemblyInteractivity], + [ArgConstants.AutoInteractivity], + [ArgConstants.GlobalInteractivity], + [ArgConstants.GlobalInteractivity, ArgConstants.WebAssemblyInteractivity], + [ArgConstants.GlobalInteractivity, ArgConstants.AutoInteractivity], + [ArgConstants.NoInteractivity, ArgConstants.UseProgramMain, ArgConstants.NoHttps, ArgConstants.Empty], + ]; + + [ConditionalTheory] + [MemberData(nameof(ArgsData))] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + public Task BlazorWebTemplate_NoAuth(string[] args) => BlazorWebTemplate_Core(args); + + [ConditionalTheory] + [MemberData(nameof(ArgsData))] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + public Task BlazorWebTemplate_IndividualAuth(string[] args) => BlazorWebTemplate_Core([ArgConstants.IndividualAuth, ..args]); + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "No LocalDb on non-Windows")] + public Task BlazorWebTemplate_IndividualAuth_LocalDb(bool useProgramMain) => useProgramMain + ? BlazorWebTemplate_Core([ArgConstants.IndividualAuth, ArgConstants.UseLocalDb, ArgConstants.UseProgramMain]) + : BlazorWebTemplate_Core([ArgConstants.IndividualAuth, ArgConstants.UseLocalDb]); + + private async Task BlazorWebTemplate_Core(string[] args) + { + var project = await ProjectFactory.CreateProject(TestOutputHelper); + + await project.RunDotNetNewAsync("blazor", args: args); + + var expectedLaunchProfileNames = args.Contains(ArgConstants.NoHttps) + ? new[] { "http", "IIS Express" } + : new[] { "http", "https", "IIS Express" }; + await project.VerifyLaunchSettings(expectedLaunchProfileNames); + + var projectFileContents = await ReadProjectFileAsync(project); + Assert.DoesNotContain("Microsoft.VisualStudio.Web.CodeGeneration.Design", projectFileContents); + Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools.DotNet", projectFileContents); + Assert.DoesNotContain("Microsoft.Extensions.SecretManager.Tools", projectFileContents); + + if (!args.Contains(ArgConstants.IndividualAuth)) + { + Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools", projectFileContents); + Assert.DoesNotContain(".db", projectFileContents); + } + else + { + Assert.Contains("Microsoft.EntityFrameworkCore.Tools", projectFileContents); + + if (args.Contains(ArgConstants.UseLocalDb)) + { + Assert.DoesNotContain(".db", projectFileContents); + } + else + { + Assert.Contains(".db", projectFileContents); + } + } + + // This can be removed once https://github.com/dotnet/razor/issues/9343 is fixed. + await WorkAroundNonNullableRenderModeAsync(project); + + // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release + // The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build + // later, while the opposite is not true. + await project.RunDotNetPublishAsync(); + await project.RunDotNetBuildAsync(); + var expectedPages = GetExpectedPages(args); + var unexpectedPages = GetUnxpectedPages(args); + + async Task VerifyProcessAsync(AspNetProcess process) + { + Assert.False( + process.Process.HasExited, + ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", project, process.Process)); + + await process.AssertPagesOk(expectedPages); + await process.AssertPagesNotFound(unexpectedPages); + + if (args.Contains(ArgConstants.IndividualAuth) && !args.Contains(ArgConstants.Empty)) + { + var response = await process.SendRequest(BlazorTemplatePages.Auth); + response.EnsureSuccessStatusCode(); + Assert.Equal("/Account/Login?ReturnUrl=%2Fauth", response.RequestMessage.RequestUri.PathAndQuery); + } + } + + using (var process = project.StartBuiltProjectAsync()) + { + await VerifyProcessAsync(process); + } + using (var process = project.StartPublishedProjectAsync()) + { + await VerifyProcessAsync(process); + } + } + + private static IEnumerable GetExpectedPages(string[] args) + { + yield return new(BlazorTemplatePages.Index); + + if (args.Contains(ArgConstants.IndividualAuth)) + { + yield return new(BlazorTemplatePages.Login); + yield return new(BlazorTemplatePages.Register); + yield return new(BlazorTemplatePages.ForgotPassword); + yield return new(BlazorTemplatePages.ResendEmailConfirmation); + } + + if (args.Contains(ArgConstants.Empty)) + { + yield break; + } + + yield return new(BlazorTemplatePages.Weather); + + if (args.Contains(ArgConstants.NoInteractivity)) + { + yield break; + } + + yield return new(BlazorTemplatePages.Counter); + } + + private static IEnumerable GetUnxpectedPages(string[] args) + { + if (!args.Contains(ArgConstants.IndividualAuth)) + { + yield return BlazorTemplatePages.Auth; + yield return BlazorTemplatePages.Login; + } + + if (args.Contains(ArgConstants.Empty)) + { + yield return BlazorTemplatePages.Weather; + yield return BlazorTemplatePages.Counter; + yield return BlazorTemplatePages.Auth; + } + + if (args.Contains(ArgConstants.NoInteractivity)) + { + yield return BlazorTemplatePages.Counter; + } + } + + private Task ReadProjectFileAsync(Project project) + { + var singleProjectPath = Path.Combine(project.TemplateOutputDir, $"{project.ProjectName}.csproj"); + if (File.Exists(singleProjectPath)) + { + return File.ReadAllTextAsync(singleProjectPath); + } + + var multiProjectPath = Path.Combine(project.TemplateOutputDir, project.ProjectName, $"{project.ProjectName}.csproj"); + if (File.Exists(multiProjectPath)) + { + // Change the TemplateOutputDir to that of the main project. + project.TemplateOutputDir = Path.GetDirectoryName(multiProjectPath); + return File.ReadAllTextAsync(multiProjectPath); + } + + throw new FailException($"Expected file to exist, but it doesn't: {singleProjectPath}"); + } + + private async Task WorkAroundNonNullableRenderModeAsync(Project project) + { + var appRazorPath = Path.Combine(project.TemplateOutputDir, "Components", "App.razor"); + var appRazorText = await File.ReadAllTextAsync(appRazorPath); + appRazorText = appRazorText.Replace("IComponentRenderMode?", "IComponentRenderMode").Replace("? null", "? null!"); + await File.WriteAllTextAsync(appRazorPath, appRazorText); + } + + private class BlazorTemplatePages + { + internal const string Index = ""; + internal const string Weather = "weather"; + internal const string Counter = "counter"; + internal const string Auth = "auth"; + internal const string Login = "Account/Login"; + internal const string Register = "Account/Register"; + internal const string ForgotPassword = "Account/ForgotPassword"; + internal const string ResendEmailConfirmation = "Account/ResendEmailConfirmation"; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/test/Templates.Tests/template-baselines.json dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/test/Templates.Tests/template-baselines.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/test/Templates.Tests/template-baselines.json 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/test/Templates.Tests/template-baselines.json 2023-11-13 13:20:34.000000000 +0000 @@ -510,11 +510,7 @@ "Files": [ "appsettings.Development.json", "appsettings.json", - "{ProjectName}.csproj", - "Program.cs", "Components/App.razor", - "Components/Routes.razor", - "Components/_Imports.razor", "Components/Layout/MainLayout.razor", "Components/Layout/MainLayout.razor.css", "Components/Layout/NavMenu.razor", @@ -522,11 +518,15 @@ "Components/Pages/Error.razor", "Components/Pages/Home.razor", "Components/Pages/Weather.razor", + "Components/Routes.razor", + "Components/_Imports.razor", + "Program.cs", "Properties/launchSettings.json", "wwwroot/app.css", - "wwwroot/favicon.png", "wwwroot/bootstrap/bootstrap.min.css", - "wwwroot/bootstrap/bootstrap.min.css.map" + "wwwroot/bootstrap/bootstrap.min.css.map", + "wwwroot/favicon.png", + "{ProjectName}.csproj" ], "AuthOption": "None" }, @@ -534,71 +534,73 @@ "Template": "blazor", "Arguments": "new blazor --interactivity none --auth Individual", "Files": [ - "app.db", "appsettings.Development.json", "appsettings.json", - "{ProjectName}.csproj", - "Program.cs", + "Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs", + "Components/Account/IdentityNoOpEmailSender.cs", + "Components/Account/IdentityRedirectManager.cs", + "Components/Account/IdentityUserAccessor.cs", + "Components/Account/Pages/ConfirmEmail.razor", + "Components/Account/Pages/ConfirmEmailChange.razor", + "Components/Account/Pages/ExternalLogin.razor", + "Components/Account/Pages/ForgotPassword.razor", + "Components/Account/Pages/ForgotPasswordConfirmation.razor", + "Components/Account/Pages/InvalidPasswordReset.razor", + "Components/Account/Pages/InvalidUser.razor", + "Components/Account/Pages/Lockout.razor", + "Components/Account/Pages/Login.razor", + "Components/Account/Pages/LoginWith2fa.razor", + "Components/Account/Pages/LoginWithRecoveryCode.razor", + "Components/Account/Pages/Manage/ChangePassword.razor", + "Components/Account/Pages/Manage/DeletePersonalData.razor", + "Components/Account/Pages/Manage/Disable2fa.razor", + "Components/Account/Pages/Manage/Email.razor", + "Components/Account/Pages/Manage/EnableAuthenticator.razor", + "Components/Account/Pages/Manage/ExternalLogins.razor", + "Components/Account/Pages/Manage/GenerateRecoveryCodes.razor", + "Components/Account/Pages/Manage/Index.razor", + "Components/Account/Pages/Manage/PersonalData.razor", + "Components/Account/Pages/Manage/ResetAuthenticator.razor", + "Components/Account/Pages/Manage/SetPassword.razor", + "Components/Account/Pages/Manage/TwoFactorAuthentication.razor", + "Components/Account/Pages/Manage/_Imports.razor", + "Components/Account/Pages/Register.razor", + "Components/Account/Pages/RegisterConfirmation.razor", + "Components/Account/Pages/ResendEmailConfirmation.razor", + "Components/Account/Pages/ResetPassword.razor", + "Components/Account/Pages/ResetPasswordConfirmation.razor", + "Components/Account/Pages/_Imports.razor", + "Components/Account/Shared/AccountLayout.razor", + "Components/Account/Shared/ExternalLoginPicker.razor", + "Components/Account/Shared/ManageLayout.razor", + "Components/Account/Shared/ManageNavMenu.razor", + "Components/Account/Shared/RedirectToLogin.razor", + "Components/Account/Shared/ShowRecoveryCodes.razor", + "Components/Account/Shared/StatusMessage.razor", "Components/App.razor", - "Components/Routes.razor", - "Components/_Imports.razor", - "Components/Identity/ExternalLoginPicker.razor", - "Components/Identity/LogoutForm.razor", - "Components/Identity/ShowRecoveryCodes.razor", - "Components/Identity/StatusMessage.razor", "Components/Layout/MainLayout.razor", "Components/Layout/MainLayout.razor.css", - "Components/Layout/ManageLayout.razor", - "Components/Layout/ManageNavMenu.razor", "Components/Layout/NavMenu.razor", "Components/Layout/NavMenu.razor.css", "Components/Pages/Auth.razor", "Components/Pages/Error.razor", "Components/Pages/Home.razor", "Components/Pages/Weather.razor", - "Components/Pages/Account/_Imports.razor", - "Components/Pages/Account/ConfirmEmail.razor", - "Components/Pages/Account/ConfirmEmailChange.razor", - "Components/Pages/Account/ExternalLogin.razor", - "Components/Pages/Account/ForgotPassword.razor", - "Components/Pages/Account/ForgotPasswordConfirmation.razor", - "Components/Pages/Account/InvalidPasswordReset.razor", - "Components/Pages/Account/InvalidUser.razor", - "Components/Pages/Account/Lockout.razor", - "Components/Pages/Account/Login.razor", - "Components/Pages/Account/LoginWith2fa.razor", - "Components/Pages/Account/LoginWithRecoveryCode.razor", - "Components/Pages/Account/Register.razor", - "Components/Pages/Account/RegisterConfirmation.razor", - "Components/Pages/Account/ResendEmailConfirmation.razor", - "Components/Pages/Account/ResetPassword.razor", - "Components/Pages/Account/ResetPasswordConfirmation.razor", - "Components/Pages/Account/Manage/_Imports.razor", - "Components/Pages/Account/Manage/ChangePassword.razor", - "Components/Pages/Account/Manage/DeletePersonalData.razor", - "Components/Pages/Account/Manage/Disable2fa.razor", - "Components/Pages/Account/Manage/Email.razor", - "Components/Pages/Account/Manage/EnableAuthenticator.razor", - "Components/Pages/Account/Manage/ExternalLogins.razor", - "Components/Pages/Account/Manage/GenerateRecoveryCodes.razor", - "Components/Pages/Account/Manage/Index.razor", - "Components/Pages/Account/Manage/PersonalData.razor", - "Components/Pages/Account/Manage/ResetAuthenticator.razor", - "Components/Pages/Account/Manage/SetPassword.razor", - "Components/Pages/Account/Manage/TwoFactorAuthentication.razor", + "Components/Routes.razor", + "Components/_Imports.razor", + "Data/app.db", "Data/ApplicationDbContext.cs", "Data/ApplicationUser.cs", - "Data/UserAccessor.cs", "Data/Migrations/00000000000000_CreateIdentitySchema.cs", "Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs", "Data/Migrations/ApplicationDbContextModelSnapshot.cs", - "Identity/Extensions/IdentityComponentsEndpointRouteBuilderExtensions.cs", - "Identity/IdentityRedirectManager.cs", + "Program.cs", "Properties/launchSettings.json", "wwwroot/app.css", - "wwwroot/favicon.png", "wwwroot/bootstrap/bootstrap.min.css", - "wwwroot/bootstrap/bootstrap.min.css.map" + "wwwroot/bootstrap/bootstrap.min.css.map", + "wwwroot/favicon.png", + "{ProjectName}.csproj" ], "AuthOption": "Individual" }, @@ -633,73 +635,75 @@ "Template": "blazor", "Arguments": "new blazor --auth Individual", "Files": [ - "app.db", "appsettings.Development.json", "appsettings.json", - "{ProjectName}.csproj", - "Program.cs", + "Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs", + "Components/Account/IdentityNoOpEmailSender.cs", + "Components/Account/IdentityRedirectManager.cs", + "Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs", + "Components/Account/IdentityUserAccessor.cs", + "Components/Account/Pages/ConfirmEmail.razor", + "Components/Account/Pages/ConfirmEmailChange.razor", + "Components/Account/Pages/ExternalLogin.razor", + "Components/Account/Pages/ForgotPassword.razor", + "Components/Account/Pages/ForgotPasswordConfirmation.razor", + "Components/Account/Pages/InvalidPasswordReset.razor", + "Components/Account/Pages/InvalidUser.razor", + "Components/Account/Pages/Lockout.razor", + "Components/Account/Pages/Login.razor", + "Components/Account/Pages/LoginWith2fa.razor", + "Components/Account/Pages/LoginWithRecoveryCode.razor", + "Components/Account/Pages/Manage/ChangePassword.razor", + "Components/Account/Pages/Manage/DeletePersonalData.razor", + "Components/Account/Pages/Manage/Disable2fa.razor", + "Components/Account/Pages/Manage/Email.razor", + "Components/Account/Pages/Manage/EnableAuthenticator.razor", + "Components/Account/Pages/Manage/ExternalLogins.razor", + "Components/Account/Pages/Manage/GenerateRecoveryCodes.razor", + "Components/Account/Pages/Manage/Index.razor", + "Components/Account/Pages/Manage/PersonalData.razor", + "Components/Account/Pages/Manage/ResetAuthenticator.razor", + "Components/Account/Pages/Manage/SetPassword.razor", + "Components/Account/Pages/Manage/TwoFactorAuthentication.razor", + "Components/Account/Pages/Manage/_Imports.razor", + "Components/Account/Pages/Register.razor", + "Components/Account/Pages/RegisterConfirmation.razor", + "Components/Account/Pages/ResendEmailConfirmation.razor", + "Components/Account/Pages/ResetPassword.razor", + "Components/Account/Pages/ResetPasswordConfirmation.razor", + "Components/Account/Pages/_Imports.razor", + "Components/Account/Shared/AccountLayout.razor", + "Components/Account/Shared/ExternalLoginPicker.razor", + "Components/Account/Shared/ManageLayout.razor", + "Components/Account/Shared/ManageNavMenu.razor", + "Components/Account/Shared/RedirectToLogin.razor", + "Components/Account/Shared/ShowRecoveryCodes.razor", + "Components/Account/Shared/StatusMessage.razor", "Components/App.razor", - "Components/Routes.razor", - "Components/_Imports.razor", - "Components/Identity/ExternalLoginPicker.razor", - "Components/Identity/LogoutForm.razor", - "Components/Identity/ShowRecoveryCodes.razor", - "Components/Identity/StatusMessage.razor", "Components/Layout/MainLayout.razor", "Components/Layout/MainLayout.razor.css", - "Components/Layout/ManageLayout.razor", - "Components/Layout/ManageNavMenu.razor", "Components/Layout/NavMenu.razor", "Components/Layout/NavMenu.razor.css", "Components/Pages/Auth.razor", - "Components/Pages/Error.razor", "Components/Pages/Counter.razor", + "Components/Pages/Error.razor", "Components/Pages/Home.razor", "Components/Pages/Weather.razor", - "Components/Pages/Account/_Imports.razor", - "Components/Pages/Account/ConfirmEmail.razor", - "Components/Pages/Account/ConfirmEmailChange.razor", - "Components/Pages/Account/ExternalLogin.razor", - "Components/Pages/Account/ForgotPassword.razor", - "Components/Pages/Account/ForgotPasswordConfirmation.razor", - "Components/Pages/Account/InvalidPasswordReset.razor", - "Components/Pages/Account/InvalidUser.razor", - "Components/Pages/Account/Lockout.razor", - "Components/Pages/Account/Login.razor", - "Components/Pages/Account/LoginWith2fa.razor", - "Components/Pages/Account/LoginWithRecoveryCode.razor", - "Components/Pages/Account/Register.razor", - "Components/Pages/Account/RegisterConfirmation.razor", - "Components/Pages/Account/ResendEmailConfirmation.razor", - "Components/Pages/Account/ResetPassword.razor", - "Components/Pages/Account/ResetPasswordConfirmation.razor", - "Components/Pages/Account/Manage/_Imports.razor", - "Components/Pages/Account/Manage/ChangePassword.razor", - "Components/Pages/Account/Manage/DeletePersonalData.razor", - "Components/Pages/Account/Manage/Disable2fa.razor", - "Components/Pages/Account/Manage/Email.razor", - "Components/Pages/Account/Manage/EnableAuthenticator.razor", - "Components/Pages/Account/Manage/ExternalLogins.razor", - "Components/Pages/Account/Manage/GenerateRecoveryCodes.razor", - "Components/Pages/Account/Manage/Index.razor", - "Components/Pages/Account/Manage/PersonalData.razor", - "Components/Pages/Account/Manage/ResetAuthenticator.razor", - "Components/Pages/Account/Manage/SetPassword.razor", - "Components/Pages/Account/Manage/TwoFactorAuthentication.razor", + "Components/Routes.razor", + "Components/_Imports.razor", + "Data/app.db", "Data/ApplicationDbContext.cs", "Data/ApplicationUser.cs", - "Data/UserAccessor.cs", "Data/Migrations/00000000000000_CreateIdentitySchema.cs", "Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs", "Data/Migrations/ApplicationDbContextModelSnapshot.cs", - "Identity/Extensions/IdentityComponentsEndpointRouteBuilderExtensions.cs", - "Identity/IdentityRedirectManager.cs", - "Identity/IdentityRevalidatingAuthenticationStateProvider.cs", + "Program.cs", "Properties/launchSettings.json", "wwwroot/app.css", - "wwwroot/favicon.png", "wwwroot/bootstrap/bootstrap.min.css", - "wwwroot/bootstrap/bootstrap.min.css.map" + "wwwroot/bootstrap/bootstrap.min.css.map", + "wwwroot/favicon.png", + "{ProjectName}.csproj" ], "AuthOption": "Individual" }, @@ -709,70 +713,72 @@ "Files": [ "appsettings.Development.json", "appsettings.json", - "{ProjectName}.csproj", - "Program.cs", + "Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs", + "Components/Account/IdentityNoOpEmailSender.cs", + "Components/Account/IdentityRedirectManager.cs", + "Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs", + "Components/Account/IdentityUserAccessor.cs", + "Components/Account/Pages/ConfirmEmail.razor", + "Components/Account/Pages/ConfirmEmailChange.razor", + "Components/Account/Pages/ExternalLogin.razor", + "Components/Account/Pages/ForgotPassword.razor", + "Components/Account/Pages/ForgotPasswordConfirmation.razor", + "Components/Account/Pages/InvalidPasswordReset.razor", + "Components/Account/Pages/InvalidUser.razor", + "Components/Account/Pages/Lockout.razor", + "Components/Account/Pages/Login.razor", + "Components/Account/Pages/LoginWith2fa.razor", + "Components/Account/Pages/LoginWithRecoveryCode.razor", + "Components/Account/Pages/Manage/ChangePassword.razor", + "Components/Account/Pages/Manage/DeletePersonalData.razor", + "Components/Account/Pages/Manage/Disable2fa.razor", + "Components/Account/Pages/Manage/Email.razor", + "Components/Account/Pages/Manage/EnableAuthenticator.razor", + "Components/Account/Pages/Manage/ExternalLogins.razor", + "Components/Account/Pages/Manage/GenerateRecoveryCodes.razor", + "Components/Account/Pages/Manage/Index.razor", + "Components/Account/Pages/Manage/PersonalData.razor", + "Components/Account/Pages/Manage/ResetAuthenticator.razor", + "Components/Account/Pages/Manage/SetPassword.razor", + "Components/Account/Pages/Manage/TwoFactorAuthentication.razor", + "Components/Account/Pages/Manage/_Imports.razor", + "Components/Account/Pages/Register.razor", + "Components/Account/Pages/RegisterConfirmation.razor", + "Components/Account/Pages/ResendEmailConfirmation.razor", + "Components/Account/Pages/ResetPassword.razor", + "Components/Account/Pages/ResetPasswordConfirmation.razor", + "Components/Account/Pages/_Imports.razor", + "Components/Account/Shared/AccountLayout.razor", + "Components/Account/Shared/ExternalLoginPicker.razor", + "Components/Account/Shared/ManageLayout.razor", + "Components/Account/Shared/ManageNavMenu.razor", + "Components/Account/Shared/RedirectToLogin.razor", + "Components/Account/Shared/ShowRecoveryCodes.razor", + "Components/Account/Shared/StatusMessage.razor", "Components/App.razor", - "Components/Routes.razor", - "Components/_Imports.razor", - "Components/Identity/ExternalLoginPicker.razor", - "Components/Identity/LogoutForm.razor", - "Components/Identity/ShowRecoveryCodes.razor", - "Components/Identity/StatusMessage.razor", "Components/Layout/MainLayout.razor", "Components/Layout/MainLayout.razor.css", - "Components/Layout/ManageLayout.razor", - "Components/Layout/ManageNavMenu.razor", "Components/Layout/NavMenu.razor", "Components/Layout/NavMenu.razor.css", "Components/Pages/Auth.razor", - "Components/Pages/Error.razor", "Components/Pages/Counter.razor", + "Components/Pages/Error.razor", "Components/Pages/Home.razor", "Components/Pages/Weather.razor", - "Components/Pages/Account/_Imports.razor", - "Components/Pages/Account/ConfirmEmail.razor", - "Components/Pages/Account/ConfirmEmailChange.razor", - "Components/Pages/Account/ExternalLogin.razor", - "Components/Pages/Account/ForgotPassword.razor", - "Components/Pages/Account/ForgotPasswordConfirmation.razor", - "Components/Pages/Account/InvalidPasswordReset.razor", - "Components/Pages/Account/InvalidUser.razor", - "Components/Pages/Account/Lockout.razor", - "Components/Pages/Account/Login.razor", - "Components/Pages/Account/LoginWith2fa.razor", - "Components/Pages/Account/LoginWithRecoveryCode.razor", - "Components/Pages/Account/Register.razor", - "Components/Pages/Account/RegisterConfirmation.razor", - "Components/Pages/Account/ResendEmailConfirmation.razor", - "Components/Pages/Account/ResetPassword.razor", - "Components/Pages/Account/ResetPasswordConfirmation.razor", - "Components/Pages/Account/Manage/_Imports.razor", - "Components/Pages/Account/Manage/ChangePassword.razor", - "Components/Pages/Account/Manage/DeletePersonalData.razor", - "Components/Pages/Account/Manage/Disable2fa.razor", - "Components/Pages/Account/Manage/Email.razor", - "Components/Pages/Account/Manage/EnableAuthenticator.razor", - "Components/Pages/Account/Manage/ExternalLogins.razor", - "Components/Pages/Account/Manage/GenerateRecoveryCodes.razor", - "Components/Pages/Account/Manage/Index.razor", - "Components/Pages/Account/Manage/PersonalData.razor", - "Components/Pages/Account/Manage/ResetAuthenticator.razor", - "Components/Pages/Account/Manage/SetPassword.razor", - "Components/Pages/Account/Manage/TwoFactorAuthentication.razor", + "Components/Routes.razor", + "Components/_Imports.razor", "Data/ApplicationDbContext.cs", "Data/ApplicationUser.cs", - "Data/UserAccessor.cs", "Data/Migrations/00000000000000_CreateIdentitySchema.cs", "Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs", "Data/Migrations/ApplicationDbContextModelSnapshot.cs", - "Identity/Extensions/IdentityComponentsEndpointRouteBuilderExtensions.cs", - "Identity/IdentityRedirectManager.cs", - "Identity/IdentityRevalidatingAuthenticationStateProvider.cs", + "Program.cs", "Properties/launchSettings.json", "wwwroot/app.css", - "wwwroot/favicon.png", "wwwroot/bootstrap/bootstrap.min.css", - "wwwroot/bootstrap/bootstrap.min.css.map" + "wwwroot/bootstrap/bootstrap.min.css.map", + "wwwroot/favicon.png", + "{ProjectName}.csproj" ], "AuthOption": "None" }, @@ -813,81 +819,83 @@ "Template": "blazor", "Arguments": "new blazor --interactivity webassembly --auth Individual", "Files": [ + "{ProjectName}.Client/{ProjectName}.Client.csproj", + "{ProjectName}.Client/Pages/Auth.razor", + "{ProjectName}.Client/Pages/Counter.razor", + "{ProjectName}.Client/PersistentAuthenticationStateProvider.cs", + "{ProjectName}.Client/Program.cs", + "{ProjectName}.Client/RedirectToLogin.razor", + "{ProjectName}.Client/UserInfo.cs", + "{ProjectName}.Client/wwwroot/appsettings.Development.json", + "{ProjectName}.Client/wwwroot/appsettings.json", + "{ProjectName}.Client/_Imports.razor", "{ProjectName}.sln", - "{ProjectName}/app.db", "{ProjectName}/appsettings.Development.json", "{ProjectName}/appsettings.json", - "{ProjectName}/{ProjectName}.csproj", - "{ProjectName}/Program.cs", + "{ProjectName}/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs", + "{ProjectName}/Components/Account/IdentityNoOpEmailSender.cs", + "{ProjectName}/Components/Account/IdentityRedirectManager.cs", + "{ProjectName}/Components/Account/IdentityUserAccessor.cs", + "{ProjectName}/Components/Account/Pages/ConfirmEmail.razor", + "{ProjectName}/Components/Account/Pages/ConfirmEmailChange.razor", + "{ProjectName}/Components/Account/Pages/ExternalLogin.razor", + "{ProjectName}/Components/Account/Pages/ForgotPassword.razor", + "{ProjectName}/Components/Account/Pages/ForgotPasswordConfirmation.razor", + "{ProjectName}/Components/Account/Pages/InvalidPasswordReset.razor", + "{ProjectName}/Components/Account/Pages/InvalidUser.razor", + "{ProjectName}/Components/Account/Pages/Lockout.razor", + "{ProjectName}/Components/Account/Pages/Login.razor", + "{ProjectName}/Components/Account/Pages/LoginWith2fa.razor", + "{ProjectName}/Components/Account/Pages/LoginWithRecoveryCode.razor", + "{ProjectName}/Components/Account/Pages/Manage/ChangePassword.razor", + "{ProjectName}/Components/Account/Pages/Manage/DeletePersonalData.razor", + "{ProjectName}/Components/Account/Pages/Manage/Disable2fa.razor", + "{ProjectName}/Components/Account/Pages/Manage/Email.razor", + "{ProjectName}/Components/Account/Pages/Manage/EnableAuthenticator.razor", + "{ProjectName}/Components/Account/Pages/Manage/ExternalLogins.razor", + "{ProjectName}/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor", + "{ProjectName}/Components/Account/Pages/Manage/Index.razor", + "{ProjectName}/Components/Account/Pages/Manage/PersonalData.razor", + "{ProjectName}/Components/Account/Pages/Manage/ResetAuthenticator.razor", + "{ProjectName}/Components/Account/Pages/Manage/SetPassword.razor", + "{ProjectName}/Components/Account/Pages/Manage/TwoFactorAuthentication.razor", + "{ProjectName}/Components/Account/Pages/Manage/_Imports.razor", + "{ProjectName}/Components/Account/Pages/Register.razor", + "{ProjectName}/Components/Account/Pages/RegisterConfirmation.razor", + "{ProjectName}/Components/Account/Pages/ResendEmailConfirmation.razor", + "{ProjectName}/Components/Account/Pages/ResetPassword.razor", + "{ProjectName}/Components/Account/Pages/ResetPasswordConfirmation.razor", + "{ProjectName}/Components/Account/Pages/_Imports.razor", + "{ProjectName}/Components/Account/PersistingServerAuthenticationStateProvider.cs", + "{ProjectName}/Components/Account/Shared/AccountLayout.razor", + "{ProjectName}/Components/Account/Shared/ExternalLoginPicker.razor", + "{ProjectName}/Components/Account/Shared/ManageLayout.razor", + "{ProjectName}/Components/Account/Shared/ManageNavMenu.razor", + "{ProjectName}/Components/Account/Shared/ShowRecoveryCodes.razor", + "{ProjectName}/Components/Account/Shared/StatusMessage.razor", "{ProjectName}/Components/App.razor", - "{ProjectName}/Components/Routes.razor", - "{ProjectName}/Components/_Imports.razor", - "{ProjectName}/Components/Identity/ExternalLoginPicker.razor", - "{ProjectName}/Components/Identity/LogoutForm.razor", - "{ProjectName}/Components/Identity/ShowRecoveryCodes.razor", - "{ProjectName}/Components/Identity/StatusMessage.razor", "{ProjectName}/Components/Layout/MainLayout.razor", "{ProjectName}/Components/Layout/MainLayout.razor.css", - "{ProjectName}/Components/Layout/ManageLayout.razor", - "{ProjectName}/Components/Layout/ManageNavMenu.razor", "{ProjectName}/Components/Layout/NavMenu.razor", "{ProjectName}/Components/Layout/NavMenu.razor.css", "{ProjectName}/Components/Pages/Error.razor", "{ProjectName}/Components/Pages/Home.razor", "{ProjectName}/Components/Pages/Weather.razor", - "{ProjectName}/Components/Pages/Account/_Imports.razor", - "{ProjectName}/Components/Pages/Account/ConfirmEmail.razor", - "{ProjectName}/Components/Pages/Account/ConfirmEmailChange.razor", - "{ProjectName}/Components/Pages/Account/ExternalLogin.razor", - "{ProjectName}/Components/Pages/Account/ForgotPassword.razor", - "{ProjectName}/Components/Pages/Account/ForgotPasswordConfirmation.razor", - "{ProjectName}/Components/Pages/Account/InvalidPasswordReset.razor", - "{ProjectName}/Components/Pages/Account/InvalidUser.razor", - "{ProjectName}/Components/Pages/Account/Lockout.razor", - "{ProjectName}/Components/Pages/Account/Login.razor", - "{ProjectName}/Components/Pages/Account/LoginWith2fa.razor", - "{ProjectName}/Components/Pages/Account/LoginWithRecoveryCode.razor", - "{ProjectName}/Components/Pages/Account/Register.razor", - "{ProjectName}/Components/Pages/Account/RegisterConfirmation.razor", - "{ProjectName}/Components/Pages/Account/ResendEmailConfirmation.razor", - "{ProjectName}/Components/Pages/Account/ResetPassword.razor", - "{ProjectName}/Components/Pages/Account/ResetPasswordConfirmation.razor", - "{ProjectName}/Components/Pages/Account/Manage/_Imports.razor", - "{ProjectName}/Components/Pages/Account/Manage/ChangePassword.razor", - "{ProjectName}/Components/Pages/Account/Manage/DeletePersonalData.razor", - "{ProjectName}/Components/Pages/Account/Manage/Disable2fa.razor", - "{ProjectName}/Components/Pages/Account/Manage/Email.razor", - "{ProjectName}/Components/Pages/Account/Manage/EnableAuthenticator.razor", - "{ProjectName}/Components/Pages/Account/Manage/ExternalLogins.razor", - "{ProjectName}/Components/Pages/Account/Manage/GenerateRecoveryCodes.razor", - "{ProjectName}/Components/Pages/Account/Manage/Index.razor", - "{ProjectName}/Components/Pages/Account/Manage/PersonalData.razor", - "{ProjectName}/Components/Pages/Account/Manage/ResetAuthenticator.razor", - "{ProjectName}/Components/Pages/Account/Manage/SetPassword.razor", - "{ProjectName}/Components/Pages/Account/Manage/TwoFactorAuthentication.razor", + "{ProjectName}/Components/Routes.razor", + "{ProjectName}/Components/_Imports.razor", + "{ProjectName}/Data/app.db", "{ProjectName}/Data/ApplicationDbContext.cs", "{ProjectName}/Data/ApplicationUser.cs", - "{ProjectName}/Data/UserAccessor.cs", "{ProjectName}/Data/Migrations/00000000000000_CreateIdentitySchema.cs", "{ProjectName}/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs", "{ProjectName}/Data/Migrations/ApplicationDbContextModelSnapshot.cs", - "{ProjectName}/Identity/Extensions/IdentityComponentsEndpointRouteBuilderExtensions.cs", - "{ProjectName}/Identity/IdentityRedirectManager.cs", - "{ProjectName}/Identity/PersistingServerAuthenticationStateProvider.cs", + "{ProjectName}/{ProjectName}.csproj", + "{ProjectName}/Program.cs", "{ProjectName}/Properties/launchSettings.json", "{ProjectName}/wwwroot/app.css", - "{ProjectName}/wwwroot/favicon.png", "{ProjectName}/wwwroot/bootstrap/bootstrap.min.css", "{ProjectName}/wwwroot/bootstrap/bootstrap.min.css.map", - "{ProjectName}.Client/{ProjectName}.Client.csproj", - "{ProjectName}.Client/PersistentAuthenticationStateProvider.cs", - "{ProjectName}.Client/Program.cs", - "{ProjectName}.Client/UserInfo.cs", - "{ProjectName}.Client/_Imports.razor", - "{ProjectName}.Client/Pages/Auth.razor", - "{ProjectName}.Client/Pages/Counter.razor", - "{ProjectName}.Client/wwwroot/appsettings.Development.json", - "{ProjectName}.Client/wwwroot/appsettings.json" + "{ProjectName}/wwwroot/favicon.png" ], "AuthOption": "Individual" }, @@ -928,81 +936,83 @@ "Template": "blazor", "Arguments": "new blazor --interactivity auto --auth Individual", "Files": [ + "{ProjectName}.Client/{ProjectName}.Client.csproj", + "{ProjectName}.Client/Pages/Auth.razor", + "{ProjectName}.Client/Pages/Counter.razor", + "{ProjectName}.Client/PersistentAuthenticationStateProvider.cs", + "{ProjectName}.Client/Program.cs", + "{ProjectName}.Client/RedirectToLogin.razor", + "{ProjectName}.Client/UserInfo.cs", + "{ProjectName}.Client/wwwroot/appsettings.Development.json", + "{ProjectName}.Client/wwwroot/appsettings.json", + "{ProjectName}.Client/_Imports.razor", "{ProjectName}.sln", - "{ProjectName}/app.db", "{ProjectName}/appsettings.Development.json", "{ProjectName}/appsettings.json", - "{ProjectName}/{ProjectName}.csproj", - "{ProjectName}/Program.cs", + "{ProjectName}/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs", + "{ProjectName}/Components/Account/IdentityNoOpEmailSender.cs", + "{ProjectName}/Components/Account/IdentityRedirectManager.cs", + "{ProjectName}/Components/Account/IdentityUserAccessor.cs", + "{ProjectName}/Components/Account/Pages/ConfirmEmail.razor", + "{ProjectName}/Components/Account/Pages/ConfirmEmailChange.razor", + "{ProjectName}/Components/Account/Pages/ExternalLogin.razor", + "{ProjectName}/Components/Account/Pages/ForgotPassword.razor", + "{ProjectName}/Components/Account/Pages/ForgotPasswordConfirmation.razor", + "{ProjectName}/Components/Account/Pages/InvalidPasswordReset.razor", + "{ProjectName}/Components/Account/Pages/InvalidUser.razor", + "{ProjectName}/Components/Account/Pages/Lockout.razor", + "{ProjectName}/Components/Account/Pages/Login.razor", + "{ProjectName}/Components/Account/Pages/LoginWith2fa.razor", + "{ProjectName}/Components/Account/Pages/LoginWithRecoveryCode.razor", + "{ProjectName}/Components/Account/Pages/Manage/ChangePassword.razor", + "{ProjectName}/Components/Account/Pages/Manage/DeletePersonalData.razor", + "{ProjectName}/Components/Account/Pages/Manage/Disable2fa.razor", + "{ProjectName}/Components/Account/Pages/Manage/Email.razor", + "{ProjectName}/Components/Account/Pages/Manage/EnableAuthenticator.razor", + "{ProjectName}/Components/Account/Pages/Manage/ExternalLogins.razor", + "{ProjectName}/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor", + "{ProjectName}/Components/Account/Pages/Manage/Index.razor", + "{ProjectName}/Components/Account/Pages/Manage/PersonalData.razor", + "{ProjectName}/Components/Account/Pages/Manage/ResetAuthenticator.razor", + "{ProjectName}/Components/Account/Pages/Manage/SetPassword.razor", + "{ProjectName}/Components/Account/Pages/Manage/TwoFactorAuthentication.razor", + "{ProjectName}/Components/Account/Pages/Manage/_Imports.razor", + "{ProjectName}/Components/Account/Pages/Register.razor", + "{ProjectName}/Components/Account/Pages/RegisterConfirmation.razor", + "{ProjectName}/Components/Account/Pages/ResendEmailConfirmation.razor", + "{ProjectName}/Components/Account/Pages/ResetPassword.razor", + "{ProjectName}/Components/Account/Pages/ResetPasswordConfirmation.razor", + "{ProjectName}/Components/Account/Pages/_Imports.razor", + "{ProjectName}/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs", + "{ProjectName}/Components/Account/Shared/AccountLayout.razor", + "{ProjectName}/Components/Account/Shared/ExternalLoginPicker.razor", + "{ProjectName}/Components/Account/Shared/ManageLayout.razor", + "{ProjectName}/Components/Account/Shared/ManageNavMenu.razor", + "{ProjectName}/Components/Account/Shared/ShowRecoveryCodes.razor", + "{ProjectName}/Components/Account/Shared/StatusMessage.razor", "{ProjectName}/Components/App.razor", - "{ProjectName}/Components/Routes.razor", - "{ProjectName}/Components/_Imports.razor", - "{ProjectName}/Components/Identity/ExternalLoginPicker.razor", - "{ProjectName}/Components/Identity/LogoutForm.razor", - "{ProjectName}/Components/Identity/ShowRecoveryCodes.razor", - "{ProjectName}/Components/Identity/StatusMessage.razor", "{ProjectName}/Components/Layout/MainLayout.razor", "{ProjectName}/Components/Layout/MainLayout.razor.css", - "{ProjectName}/Components/Layout/ManageLayout.razor", - "{ProjectName}/Components/Layout/ManageNavMenu.razor", "{ProjectName}/Components/Layout/NavMenu.razor", "{ProjectName}/Components/Layout/NavMenu.razor.css", "{ProjectName}/Components/Pages/Error.razor", "{ProjectName}/Components/Pages/Home.razor", "{ProjectName}/Components/Pages/Weather.razor", - "{ProjectName}/Components/Pages/Account/_Imports.razor", - "{ProjectName}/Components/Pages/Account/ConfirmEmail.razor", - "{ProjectName}/Components/Pages/Account/ConfirmEmailChange.razor", - "{ProjectName}/Components/Pages/Account/ExternalLogin.razor", - "{ProjectName}/Components/Pages/Account/ForgotPassword.razor", - "{ProjectName}/Components/Pages/Account/ForgotPasswordConfirmation.razor", - "{ProjectName}/Components/Pages/Account/InvalidPasswordReset.razor", - "{ProjectName}/Components/Pages/Account/InvalidUser.razor", - "{ProjectName}/Components/Pages/Account/Lockout.razor", - "{ProjectName}/Components/Pages/Account/Login.razor", - "{ProjectName}/Components/Pages/Account/LoginWith2fa.razor", - "{ProjectName}/Components/Pages/Account/LoginWithRecoveryCode.razor", - "{ProjectName}/Components/Pages/Account/Register.razor", - "{ProjectName}/Components/Pages/Account/RegisterConfirmation.razor", - "{ProjectName}/Components/Pages/Account/ResendEmailConfirmation.razor", - "{ProjectName}/Components/Pages/Account/ResetPassword.razor", - "{ProjectName}/Components/Pages/Account/ResetPasswordConfirmation.razor", - "{ProjectName}/Components/Pages/Account/Manage/_Imports.razor", - "{ProjectName}/Components/Pages/Account/Manage/ChangePassword.razor", - "{ProjectName}/Components/Pages/Account/Manage/DeletePersonalData.razor", - "{ProjectName}/Components/Pages/Account/Manage/Disable2fa.razor", - "{ProjectName}/Components/Pages/Account/Manage/Email.razor", - "{ProjectName}/Components/Pages/Account/Manage/EnableAuthenticator.razor", - "{ProjectName}/Components/Pages/Account/Manage/ExternalLogins.razor", - "{ProjectName}/Components/Pages/Account/Manage/GenerateRecoveryCodes.razor", - "{ProjectName}/Components/Pages/Account/Manage/Index.razor", - "{ProjectName}/Components/Pages/Account/Manage/PersonalData.razor", - "{ProjectName}/Components/Pages/Account/Manage/ResetAuthenticator.razor", - "{ProjectName}/Components/Pages/Account/Manage/SetPassword.razor", - "{ProjectName}/Components/Pages/Account/Manage/TwoFactorAuthentication.razor", + "{ProjectName}/Components/Routes.razor", + "{ProjectName}/Components/_Imports.razor", + "{ProjectName}/Data/app.db", "{ProjectName}/Data/ApplicationDbContext.cs", "{ProjectName}/Data/ApplicationUser.cs", - "{ProjectName}/Data/UserAccessor.cs", "{ProjectName}/Data/Migrations/00000000000000_CreateIdentitySchema.cs", "{ProjectName}/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs", "{ProjectName}/Data/Migrations/ApplicationDbContextModelSnapshot.cs", - "{ProjectName}/Identity/Extensions/IdentityComponentsEndpointRouteBuilderExtensions.cs", - "{ProjectName}/Identity/IdentityRedirectManager.cs", - "{ProjectName}/Identity/PersistingRevalidatingAuthenticationStateProvider.cs", + "{ProjectName}/{ProjectName}.csproj", + "{ProjectName}/Program.cs", "{ProjectName}/Properties/launchSettings.json", "{ProjectName}/wwwroot/app.css", - "{ProjectName}/wwwroot/favicon.png", "{ProjectName}/wwwroot/bootstrap/bootstrap.min.css", "{ProjectName}/wwwroot/bootstrap/bootstrap.min.css.map", - "{ProjectName}.Client/{ProjectName}.Client.csproj", - "{ProjectName}.Client/PersistentAuthenticationStateProvider.cs", - "{ProjectName}.Client/Program.cs", - "{ProjectName}.Client/UserInfo.cs", - "{ProjectName}.Client/_Imports.razor", - "{ProjectName}.Client/Pages/Auth.razor", - "{ProjectName}.Client/Pages/Counter.razor", - "{ProjectName}.Client/wwwroot/appsettings.Development.json", - "{ProjectName}.Client/wwwroot/appsettings.json" + "{ProjectName}/wwwroot/favicon.png" ], "AuthOption": "Individual" }, @@ -1037,65 +1047,65 @@ "Template": "blazor", "Arguments": "new blazor --interactivity webassembly --all-interactive", "Files": [ - "{ProjectName}/appsettings.Development.json", - "{ProjectName}/appsettings.json", - "{ProjectName}/Components/App.razor", - "{ProjectName}/Components/_Imports.razor", - "{ProjectName}/Program.cs", - "{ProjectName}/Properties/launchSettings.json", - "{ProjectName}/{ProjectName}.csproj", - "{ProjectName}/wwwroot/app.css", - "{ProjectName}/wwwroot/bootstrap/bootstrap.min.css", - "{ProjectName}/wwwroot/bootstrap/bootstrap.min.css.map", - "{ProjectName}/wwwroot/favicon.png", "{ProjectName}.Client/Layout/MainLayout.razor", "{ProjectName}.Client/Layout/MainLayout.razor.css", "{ProjectName}.Client/Layout/NavMenu.razor", "{ProjectName}.Client/Layout/NavMenu.razor.css", "{ProjectName}.Client/Pages/Counter.razor", - "{ProjectName}.Client/Pages/Error.razor", "{ProjectName}.Client/Pages/Home.razor", "{ProjectName}.Client/Pages/Weather.razor", "{ProjectName}.Client/Program.cs", "{ProjectName}.Client/Routes.razor", - "{ProjectName}.Client/{ProjectName}.Client.csproj", "{ProjectName}.Client/wwwroot/appsettings.Development.json", "{ProjectName}.Client/wwwroot/appsettings.json", "{ProjectName}.Client/_Imports.razor", - "{ProjectName}.sln" - ], - "AuthOption": "None" - }, - "UseServerAndWebAssemblyInteractiveAtRoot": { - "Template": "blazor", - "Arguments": "new blazor --interactivity auto --all-interactive", - "Files": [ + "{ProjectName}.Client/{ProjectName}.Client.csproj", + "{ProjectName}.sln", "{ProjectName}/appsettings.Development.json", "{ProjectName}/appsettings.json", "{ProjectName}/Components/App.razor", + "{ProjectName}/Components/Pages/Error.razor", "{ProjectName}/Components/_Imports.razor", "{ProjectName}/Program.cs", "{ProjectName}/Properties/launchSettings.json", - "{ProjectName}/{ProjectName}.csproj", "{ProjectName}/wwwroot/app.css", "{ProjectName}/wwwroot/bootstrap/bootstrap.min.css", "{ProjectName}/wwwroot/bootstrap/bootstrap.min.css.map", "{ProjectName}/wwwroot/favicon.png", + "{ProjectName}/{ProjectName}.csproj" + ], + "AuthOption": "None" + }, + "UseServerAndWebAssemblyInteractiveAtRoot": { + "Template": "blazor", + "Arguments": "new blazor --interactivity auto --all-interactive", + "Files": [ "{ProjectName}.Client/Layout/MainLayout.razor", "{ProjectName}.Client/Layout/MainLayout.razor.css", "{ProjectName}.Client/Layout/NavMenu.razor", "{ProjectName}.Client/Layout/NavMenu.razor.css", "{ProjectName}.Client/Pages/Counter.razor", - "{ProjectName}.Client/Pages/Error.razor", "{ProjectName}.Client/Pages/Home.razor", "{ProjectName}.Client/Pages/Weather.razor", "{ProjectName}.Client/Program.cs", "{ProjectName}.Client/Routes.razor", - "{ProjectName}.Client/{ProjectName}.Client.csproj", "{ProjectName}.Client/wwwroot/appsettings.Development.json", "{ProjectName}.Client/wwwroot/appsettings.json", "{ProjectName}.Client/_Imports.razor", - "{ProjectName}.sln" + "{ProjectName}.Client/{ProjectName}.Client.csproj", + "{ProjectName}.sln", + "{ProjectName}/appsettings.Development.json", + "{ProjectName}/appsettings.json", + "{ProjectName}/Components/App.razor", + "{ProjectName}/Components/Pages/Error.razor", + "{ProjectName}/Components/_Imports.razor", + "{ProjectName}/Program.cs", + "{ProjectName}/Properties/launchSettings.json", + "{ProjectName}/wwwroot/app.css", + "{ProjectName}/wwwroot/bootstrap/bootstrap.min.css", + "{ProjectName}/wwwroot/bootstrap/bootstrap.min.css.map", + "{ProjectName}/wwwroot/favicon.png", + "{ProjectName}/{ProjectName}.csproj" ], "AuthOption": "None" }, @@ -1191,71 +1201,317 @@ "Template": "blazor", "Arguments": "new blazor --interactivity auto --empty --auth Individual", "Files": [ + "{ProjectName}.Client/{ProjectName}.Client.csproj", + "{ProjectName}.Client/PersistentAuthenticationStateProvider.cs", + "{ProjectName}.Client/Program.cs", + "{ProjectName}.Client/RedirectToLogin.razor", + "{ProjectName}.Client/UserInfo.cs", + "{ProjectName}.Client/_Imports.razor", "{ProjectName}.sln", - "{ProjectName}/app.db", "{ProjectName}/appsettings.Development.json", "{ProjectName}/appsettings.json", - "{ProjectName}/{ProjectName}.csproj", - "{ProjectName}/Program.cs", + "{ProjectName}/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs", + "{ProjectName}/Components/Account/IdentityNoOpEmailSender.cs", + "{ProjectName}/Components/Account/IdentityRedirectManager.cs", + "{ProjectName}/Components/Account/IdentityUserAccessor.cs", + "{ProjectName}/Components/Account/Pages/ConfirmEmail.razor", + "{ProjectName}/Components/Account/Pages/ConfirmEmailChange.razor", + "{ProjectName}/Components/Account/Pages/ExternalLogin.razor", + "{ProjectName}/Components/Account/Pages/ForgotPassword.razor", + "{ProjectName}/Components/Account/Pages/ForgotPasswordConfirmation.razor", + "{ProjectName}/Components/Account/Pages/InvalidPasswordReset.razor", + "{ProjectName}/Components/Account/Pages/InvalidUser.razor", + "{ProjectName}/Components/Account/Pages/Lockout.razor", + "{ProjectName}/Components/Account/Pages/Login.razor", + "{ProjectName}/Components/Account/Pages/LoginWith2fa.razor", + "{ProjectName}/Components/Account/Pages/LoginWithRecoveryCode.razor", + "{ProjectName}/Components/Account/Pages/Manage/ChangePassword.razor", + "{ProjectName}/Components/Account/Pages/Manage/DeletePersonalData.razor", + "{ProjectName}/Components/Account/Pages/Manage/Disable2fa.razor", + "{ProjectName}/Components/Account/Pages/Manage/Email.razor", + "{ProjectName}/Components/Account/Pages/Manage/EnableAuthenticator.razor", + "{ProjectName}/Components/Account/Pages/Manage/ExternalLogins.razor", + "{ProjectName}/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor", + "{ProjectName}/Components/Account/Pages/Manage/Index.razor", + "{ProjectName}/Components/Account/Pages/Manage/PersonalData.razor", + "{ProjectName}/Components/Account/Pages/Manage/ResetAuthenticator.razor", + "{ProjectName}/Components/Account/Pages/Manage/SetPassword.razor", + "{ProjectName}/Components/Account/Pages/Manage/TwoFactorAuthentication.razor", + "{ProjectName}/Components/Account/Pages/Manage/_Imports.razor", + "{ProjectName}/Components/Account/Pages/Register.razor", + "{ProjectName}/Components/Account/Pages/RegisterConfirmation.razor", + "{ProjectName}/Components/Account/Pages/ResendEmailConfirmation.razor", + "{ProjectName}/Components/Account/Pages/ResetPassword.razor", + "{ProjectName}/Components/Account/Pages/ResetPasswordConfirmation.razor", + "{ProjectName}/Components/Account/Pages/_Imports.razor", + "{ProjectName}/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs", + "{ProjectName}/Components/Account/Shared/AccountLayout.razor", + "{ProjectName}/Components/Account/Shared/ExternalLoginPicker.razor", + "{ProjectName}/Components/Account/Shared/ManageLayout.razor", + "{ProjectName}/Components/Account/Shared/ManageNavMenu.razor", + "{ProjectName}/Components/Account/Shared/ShowRecoveryCodes.razor", + "{ProjectName}/Components/Account/Shared/StatusMessage.razor", "{ProjectName}/Components/App.razor", - "{ProjectName}/Components/Routes.razor", - "{ProjectName}/Components/_Imports.razor", - "{ProjectName}/Components/Identity/ExternalLoginPicker.razor", - "{ProjectName}/Components/Identity/LogoutForm.razor", - "{ProjectName}/Components/Identity/ShowRecoveryCodes.razor", - "{ProjectName}/Components/Identity/StatusMessage.razor", "{ProjectName}/Components/Layout/MainLayout.razor", "{ProjectName}/Components/Layout/MainLayout.razor.css", - "{ProjectName}/Components/Layout/ManageLayout.razor", - "{ProjectName}/Components/Layout/ManageNavMenu.razor", "{ProjectName}/Components/Pages/Error.razor", "{ProjectName}/Components/Pages/Home.razor", - "{ProjectName}/Components/Pages/Account/_Imports.razor", - "{ProjectName}/Components/Pages/Account/ConfirmEmail.razor", - "{ProjectName}/Components/Pages/Account/ConfirmEmailChange.razor", - "{ProjectName}/Components/Pages/Account/ExternalLogin.razor", - "{ProjectName}/Components/Pages/Account/ForgotPassword.razor", - "{ProjectName}/Components/Pages/Account/ForgotPasswordConfirmation.razor", - "{ProjectName}/Components/Pages/Account/InvalidPasswordReset.razor", - "{ProjectName}/Components/Pages/Account/InvalidUser.razor", - "{ProjectName}/Components/Pages/Account/Lockout.razor", - "{ProjectName}/Components/Pages/Account/Login.razor", - "{ProjectName}/Components/Pages/Account/LoginWith2fa.razor", - "{ProjectName}/Components/Pages/Account/LoginWithRecoveryCode.razor", - "{ProjectName}/Components/Pages/Account/Register.razor", - "{ProjectName}/Components/Pages/Account/RegisterConfirmation.razor", - "{ProjectName}/Components/Pages/Account/ResendEmailConfirmation.razor", - "{ProjectName}/Components/Pages/Account/ResetPassword.razor", - "{ProjectName}/Components/Pages/Account/ResetPasswordConfirmation.razor", - "{ProjectName}/Components/Pages/Account/Manage/_Imports.razor", - "{ProjectName}/Components/Pages/Account/Manage/ChangePassword.razor", - "{ProjectName}/Components/Pages/Account/Manage/DeletePersonalData.razor", - "{ProjectName}/Components/Pages/Account/Manage/Disable2fa.razor", - "{ProjectName}/Components/Pages/Account/Manage/Email.razor", - "{ProjectName}/Components/Pages/Account/Manage/EnableAuthenticator.razor", - "{ProjectName}/Components/Pages/Account/Manage/ExternalLogins.razor", - "{ProjectName}/Components/Pages/Account/Manage/GenerateRecoveryCodes.razor", - "{ProjectName}/Components/Pages/Account/Manage/Index.razor", - "{ProjectName}/Components/Pages/Account/Manage/PersonalData.razor", - "{ProjectName}/Components/Pages/Account/Manage/ResetAuthenticator.razor", - "{ProjectName}/Components/Pages/Account/Manage/SetPassword.razor", - "{ProjectName}/Components/Pages/Account/Manage/TwoFactorAuthentication.razor", + "{ProjectName}/Components/Routes.razor", + "{ProjectName}/Components/_Imports.razor", + "{ProjectName}/Data/app.db", + "{ProjectName}/Data/ApplicationDbContext.cs", + "{ProjectName}/Data/ApplicationUser.cs", + "{ProjectName}/Data/Migrations/00000000000000_CreateIdentitySchema.cs", + "{ProjectName}/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs", + "{ProjectName}/Data/Migrations/ApplicationDbContextModelSnapshot.cs", + "{ProjectName}/{ProjectName}.csproj", + "{ProjectName}/Program.cs", + "{ProjectName}/Properties/launchSettings.json", + "{ProjectName}/wwwroot/app.css" + ], + "AuthOption": "Individual" + }, + "ServerInteractiveAtRootWithIndividualAuth": { + "Template": "blazor", + "Arguments": "new blazor --all-interactive --auth Individual", + "Files": [ + "appsettings.Development.json", + "appsettings.json", + "Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs", + "Components/Account/IdentityNoOpEmailSender.cs", + "Components/Account/IdentityRedirectManager.cs", + "Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs", + "Components/Account/IdentityUserAccessor.cs", + "Components/Account/Pages/ConfirmEmail.razor", + "Components/Account/Pages/ConfirmEmailChange.razor", + "Components/Account/Pages/ExternalLogin.razor", + "Components/Account/Pages/ForgotPassword.razor", + "Components/Account/Pages/ForgotPasswordConfirmation.razor", + "Components/Account/Pages/InvalidPasswordReset.razor", + "Components/Account/Pages/InvalidUser.razor", + "Components/Account/Pages/Lockout.razor", + "Components/Account/Pages/Login.razor", + "Components/Account/Pages/LoginWith2fa.razor", + "Components/Account/Pages/LoginWithRecoveryCode.razor", + "Components/Account/Pages/Manage/ChangePassword.razor", + "Components/Account/Pages/Manage/DeletePersonalData.razor", + "Components/Account/Pages/Manage/Disable2fa.razor", + "Components/Account/Pages/Manage/Email.razor", + "Components/Account/Pages/Manage/EnableAuthenticator.razor", + "Components/Account/Pages/Manage/ExternalLogins.razor", + "Components/Account/Pages/Manage/GenerateRecoveryCodes.razor", + "Components/Account/Pages/Manage/Index.razor", + "Components/Account/Pages/Manage/PersonalData.razor", + "Components/Account/Pages/Manage/ResetAuthenticator.razor", + "Components/Account/Pages/Manage/SetPassword.razor", + "Components/Account/Pages/Manage/TwoFactorAuthentication.razor", + "Components/Account/Pages/Manage/_Imports.razor", + "Components/Account/Pages/Register.razor", + "Components/Account/Pages/RegisterConfirmation.razor", + "Components/Account/Pages/ResendEmailConfirmation.razor", + "Components/Account/Pages/ResetPassword.razor", + "Components/Account/Pages/ResetPasswordConfirmation.razor", + "Components/Account/Pages/_Imports.razor", + "Components/Account/Shared/AccountLayout.razor", + "Components/Account/Shared/ExternalLoginPicker.razor", + "Components/Account/Shared/ManageLayout.razor", + "Components/Account/Shared/ManageNavMenu.razor", + "Components/Account/Shared/RedirectToLogin.razor", + "Components/Account/Shared/ShowRecoveryCodes.razor", + "Components/Account/Shared/StatusMessage.razor", + "Components/App.razor", + "Components/Layout/MainLayout.razor", + "Components/Layout/MainLayout.razor.css", + "Components/Layout/NavMenu.razor", + "Components/Layout/NavMenu.razor.css", + "Components/Pages/Auth.razor", + "Components/Pages/Counter.razor", + "Components/Pages/Error.razor", + "Components/Pages/Home.razor", + "Components/Pages/Weather.razor", + "Components/Routes.razor", + "Components/_Imports.razor", + "Data/app.db", + "Data/ApplicationDbContext.cs", + "Data/ApplicationUser.cs", + "Data/Migrations/00000000000000_CreateIdentitySchema.cs", + "Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs", + "Data/Migrations/ApplicationDbContextModelSnapshot.cs", + "Program.cs", + "Properties/launchSettings.json", + "wwwroot/app.css", + "wwwroot/bootstrap/bootstrap.min.css", + "wwwroot/bootstrap/bootstrap.min.css.map", + "wwwroot/favicon.png", + "{ProjectName}.csproj" + ], + "AuthOption": "Individual" + }, + "WebAssemblyInteractiveAtRootWithIndividualAuth": { + "Template": "blazor", + "Arguments": "new blazor --interactivity WebAssembly --all-interactive --auth Individual", + "Files": [ + "{ProjectName}.Client/Layout/MainLayout.razor", + "{ProjectName}.Client/Layout/MainLayout.razor.css", + "{ProjectName}.Client/Layout/NavMenu.razor", + "{ProjectName}.Client/Layout/NavMenu.razor.css", + "{ProjectName}.Client/{ProjectName}.Client.csproj", + "{ProjectName}.Client/Pages/Auth.razor", + "{ProjectName}.Client/Pages/Counter.razor", + "{ProjectName}.Client/Pages/Home.razor", + "{ProjectName}.Client/Pages/Weather.razor", + "{ProjectName}.Client/PersistentAuthenticationStateProvider.cs", + "{ProjectName}.Client/Program.cs", + "{ProjectName}.Client/RedirectToLogin.razor", + "{ProjectName}.Client/Routes.razor", + "{ProjectName}.Client/UserInfo.cs", + "{ProjectName}.Client/wwwroot/appsettings.Development.json", + "{ProjectName}.Client/wwwroot/appsettings.json", + "{ProjectName}.Client/_Imports.razor", + "{ProjectName}.sln", + "{ProjectName}/appsettings.Development.json", + "{ProjectName}/appsettings.json", + "{ProjectName}/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs", + "{ProjectName}/Components/Account/IdentityNoOpEmailSender.cs", + "{ProjectName}/Components/Account/IdentityRedirectManager.cs", + "{ProjectName}/Components/Account/IdentityUserAccessor.cs", + "{ProjectName}/Components/Account/Pages/ConfirmEmail.razor", + "{ProjectName}/Components/Account/Pages/ConfirmEmailChange.razor", + "{ProjectName}/Components/Account/Pages/ExternalLogin.razor", + "{ProjectName}/Components/Account/Pages/ForgotPassword.razor", + "{ProjectName}/Components/Account/Pages/ForgotPasswordConfirmation.razor", + "{ProjectName}/Components/Account/Pages/InvalidPasswordReset.razor", + "{ProjectName}/Components/Account/Pages/InvalidUser.razor", + "{ProjectName}/Components/Account/Pages/Lockout.razor", + "{ProjectName}/Components/Account/Pages/Login.razor", + "{ProjectName}/Components/Account/Pages/LoginWith2fa.razor", + "{ProjectName}/Components/Account/Pages/LoginWithRecoveryCode.razor", + "{ProjectName}/Components/Account/Pages/Manage/ChangePassword.razor", + "{ProjectName}/Components/Account/Pages/Manage/DeletePersonalData.razor", + "{ProjectName}/Components/Account/Pages/Manage/Disable2fa.razor", + "{ProjectName}/Components/Account/Pages/Manage/Email.razor", + "{ProjectName}/Components/Account/Pages/Manage/EnableAuthenticator.razor", + "{ProjectName}/Components/Account/Pages/Manage/ExternalLogins.razor", + "{ProjectName}/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor", + "{ProjectName}/Components/Account/Pages/Manage/Index.razor", + "{ProjectName}/Components/Account/Pages/Manage/PersonalData.razor", + "{ProjectName}/Components/Account/Pages/Manage/ResetAuthenticator.razor", + "{ProjectName}/Components/Account/Pages/Manage/SetPassword.razor", + "{ProjectName}/Components/Account/Pages/Manage/TwoFactorAuthentication.razor", + "{ProjectName}/Components/Account/Pages/Manage/_Imports.razor", + "{ProjectName}/Components/Account/Pages/Register.razor", + "{ProjectName}/Components/Account/Pages/RegisterConfirmation.razor", + "{ProjectName}/Components/Account/Pages/ResendEmailConfirmation.razor", + "{ProjectName}/Components/Account/Pages/ResetPassword.razor", + "{ProjectName}/Components/Account/Pages/ResetPasswordConfirmation.razor", + "{ProjectName}/Components/Account/Pages/_Imports.razor", + "{ProjectName}/Components/Account/PersistingServerAuthenticationStateProvider.cs", + "{ProjectName}/Components/Account/Shared/AccountLayout.razor", + "{ProjectName}/Components/Account/Shared/ExternalLoginPicker.razor", + "{ProjectName}/Components/Account/Shared/ManageLayout.razor", + "{ProjectName}/Components/Account/Shared/ManageNavMenu.razor", + "{ProjectName}/Components/Account/Shared/ShowRecoveryCodes.razor", + "{ProjectName}/Components/Account/Shared/StatusMessage.razor", + "{ProjectName}/Components/App.razor", + "{ProjectName}/Components/Pages/Error.razor", + "{ProjectName}/Components/_Imports.razor", + "{ProjectName}/Data/app.db", "{ProjectName}/Data/ApplicationDbContext.cs", "{ProjectName}/Data/ApplicationUser.cs", - "{ProjectName}/Data/UserAccessor.cs", "{ProjectName}/Data/Migrations/00000000000000_CreateIdentitySchema.cs", "{ProjectName}/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs", "{ProjectName}/Data/Migrations/ApplicationDbContextModelSnapshot.cs", - "{ProjectName}/Identity/Extensions/IdentityComponentsEndpointRouteBuilderExtensions.cs", - "{ProjectName}/Identity/IdentityRedirectManager.cs", - "{ProjectName}/Identity/PersistingRevalidatingAuthenticationStateProvider.cs", + "{ProjectName}/{ProjectName}.csproj", + "{ProjectName}/Program.cs", "{ProjectName}/Properties/launchSettings.json", "{ProjectName}/wwwroot/app.css", + "{ProjectName}/wwwroot/bootstrap/bootstrap.min.css", + "{ProjectName}/wwwroot/bootstrap/bootstrap.min.css.map", + "{ProjectName}/wwwroot/favicon.png" + ], + "AuthOption": "Individual" + }, + "AutoInteractiveAtRootWithIndividualAuth": { + "Template": "blazor", + "Arguments": "new blazor --interactivity Auto --all-interactive --auth Individual", + "Files": [ + "{ProjectName}.Client/Layout/MainLayout.razor", + "{ProjectName}.Client/Layout/MainLayout.razor.css", + "{ProjectName}.Client/Layout/NavMenu.razor", + "{ProjectName}.Client/Layout/NavMenu.razor.css", "{ProjectName}.Client/{ProjectName}.Client.csproj", + "{ProjectName}.Client/Pages/Auth.razor", + "{ProjectName}.Client/Pages/Counter.razor", + "{ProjectName}.Client/Pages/Home.razor", + "{ProjectName}.Client/Pages/Weather.razor", "{ProjectName}.Client/PersistentAuthenticationStateProvider.cs", "{ProjectName}.Client/Program.cs", + "{ProjectName}.Client/RedirectToLogin.razor", + "{ProjectName}.Client/Routes.razor", "{ProjectName}.Client/UserInfo.cs", - "{ProjectName}.Client/_Imports.razor" + "{ProjectName}.Client/wwwroot/appsettings.Development.json", + "{ProjectName}.Client/wwwroot/appsettings.json", + "{ProjectName}.Client/_Imports.razor", + "{ProjectName}.sln", + "{ProjectName}/appsettings.Development.json", + "{ProjectName}/appsettings.json", + "{ProjectName}/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs", + "{ProjectName}/Components/Account/IdentityNoOpEmailSender.cs", + "{ProjectName}/Components/Account/IdentityRedirectManager.cs", + "{ProjectName}/Components/Account/IdentityUserAccessor.cs", + "{ProjectName}/Components/Account/Pages/ConfirmEmail.razor", + "{ProjectName}/Components/Account/Pages/ConfirmEmailChange.razor", + "{ProjectName}/Components/Account/Pages/ExternalLogin.razor", + "{ProjectName}/Components/Account/Pages/ForgotPassword.razor", + "{ProjectName}/Components/Account/Pages/ForgotPasswordConfirmation.razor", + "{ProjectName}/Components/Account/Pages/InvalidPasswordReset.razor", + "{ProjectName}/Components/Account/Pages/InvalidUser.razor", + "{ProjectName}/Components/Account/Pages/Lockout.razor", + "{ProjectName}/Components/Account/Pages/Login.razor", + "{ProjectName}/Components/Account/Pages/LoginWith2fa.razor", + "{ProjectName}/Components/Account/Pages/LoginWithRecoveryCode.razor", + "{ProjectName}/Components/Account/Pages/Manage/ChangePassword.razor", + "{ProjectName}/Components/Account/Pages/Manage/DeletePersonalData.razor", + "{ProjectName}/Components/Account/Pages/Manage/Disable2fa.razor", + "{ProjectName}/Components/Account/Pages/Manage/Email.razor", + "{ProjectName}/Components/Account/Pages/Manage/EnableAuthenticator.razor", + "{ProjectName}/Components/Account/Pages/Manage/ExternalLogins.razor", + "{ProjectName}/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor", + "{ProjectName}/Components/Account/Pages/Manage/Index.razor", + "{ProjectName}/Components/Account/Pages/Manage/PersonalData.razor", + "{ProjectName}/Components/Account/Pages/Manage/ResetAuthenticator.razor", + "{ProjectName}/Components/Account/Pages/Manage/SetPassword.razor", + "{ProjectName}/Components/Account/Pages/Manage/TwoFactorAuthentication.razor", + "{ProjectName}/Components/Account/Pages/Manage/_Imports.razor", + "{ProjectName}/Components/Account/Pages/Register.razor", + "{ProjectName}/Components/Account/Pages/RegisterConfirmation.razor", + "{ProjectName}/Components/Account/Pages/ResendEmailConfirmation.razor", + "{ProjectName}/Components/Account/Pages/ResetPassword.razor", + "{ProjectName}/Components/Account/Pages/ResetPasswordConfirmation.razor", + "{ProjectName}/Components/Account/Pages/_Imports.razor", + "{ProjectName}/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs", + "{ProjectName}/Components/Account/Shared/AccountLayout.razor", + "{ProjectName}/Components/Account/Shared/ExternalLoginPicker.razor", + "{ProjectName}/Components/Account/Shared/ManageLayout.razor", + "{ProjectName}/Components/Account/Shared/ManageNavMenu.razor", + "{ProjectName}/Components/Account/Shared/ShowRecoveryCodes.razor", + "{ProjectName}/Components/Account/Shared/StatusMessage.razor", + "{ProjectName}/Components/App.razor", + "{ProjectName}/Components/Pages/Error.razor", + "{ProjectName}/Components/_Imports.razor", + "{ProjectName}/Data/app.db", + "{ProjectName}/Data/ApplicationDbContext.cs", + "{ProjectName}/Data/ApplicationUser.cs", + "{ProjectName}/Data/Migrations/00000000000000_CreateIdentitySchema.cs", + "{ProjectName}/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs", + "{ProjectName}/Data/Migrations/ApplicationDbContextModelSnapshot.cs", + "{ProjectName}/{ProjectName}.csproj", + "{ProjectName}/Program.cs", + "{ProjectName}/Properties/launchSettings.json", + "{ProjectName}/wwwroot/app.css", + "{ProjectName}/wwwroot/bootstrap/bootstrap.min.css", + "{ProjectName}/wwwroot/bootstrap/bootstrap.min.css.map", + "{ProjectName}/wwwroot/favicon.png" ], "AuthOption": "Individual" } @@ -2180,15 +2436,15 @@ "Arguments": "new blazorwasm", "Files": [ "Program.cs", - "Components/App.razor", - "Components/_Imports.razor", - "Components/Pages/Counter.razor", - "Components/Pages/Home.razor", - "Components/Pages/Weather.razor", - "Components/Layout/MainLayout.razor", - "Components/Layout/MainLayout.razor.css", - "Components/Layout/NavMenu.razor", - "Components/Layout/NavMenu.razor.css", + "App.razor", + "_Imports.razor", + "Pages/Counter.razor", + "Pages/Home.razor", + "Pages/Weather.razor", + "Layout/MainLayout.razor", + "Layout/MainLayout.razor.css", + "Layout/NavMenu.razor", + "Layout/NavMenu.razor.css", "Properties/launchSettings.json", "wwwroot/favicon.png", "wwwroot/icon-192.png", @@ -2204,11 +2460,12 @@ "Arguments": "new blazorwasm --empty", "Files": [ "Program.cs", - "Components/App.razor", - "Components/_Imports.razor", - "Components/Pages/Home.razor", - "Components/Layout/MainLayout.razor", + "App.razor", + "_Imports.razor", + "Pages/Home.razor", + "Layout/MainLayout.razor", "Properties/launchSettings.json", + "wwwroot/icon-192.png", "wwwroot/index.html", "wwwroot/css/app.css" ] @@ -2218,18 +2475,18 @@ "Arguments": "new blazorwasm -au Individual", "Files": [ "Program.cs", - "Components/App.razor", - "Components/_Imports.razor", - "Components/Pages/Authentication.razor", - "Components/Pages/Counter.razor", - "Components/Pages/Home.razor", - "Components/Pages/Weather.razor", - "Components/Layout/MainLayout.razor", - "Components/Layout/MainLayout.razor.css", - "Components/Layout/NavMenu.razor", - "Components/Layout/NavMenu.razor.css", - "Components/Login/LoginDisplay.razor", - "Components/Login/RedirectToLogin.razor", + "App.razor", + "_Imports.razor", + "Pages/Authentication.razor", + "Pages/Counter.razor", + "Pages/Home.razor", + "Pages/Weather.razor", + "Layout/MainLayout.razor", + "Layout/MainLayout.razor.css", + "Layout/NavMenu.razor", + "Layout/NavMenu.razor.css", + "Layout/LoginDisplay.razor", + "Layout/RedirectToLogin.razor", "Properties/launchSettings.json", "wwwroot/appsettings.Development.json", "wwwroot/appsettings.json", @@ -2247,16 +2504,17 @@ "Arguments": "new blazorwasm -au Individual --empty", "Files": [ "Program.cs", - "Components/App.razor", - "Components/_Imports.razor", - "Components/Pages/Authentication.razor", - "Components/Pages/Home.razor", - "Components/Layout/MainLayout.razor", - "Components/Login/LoginDisplay.razor", - "Components/Login/RedirectToLogin.razor", + "App.razor", + "_Imports.razor", + "Pages/Authentication.razor", + "Pages/Home.razor", + "Layout/MainLayout.razor", + "Layout/LoginDisplay.razor", + "Layout/RedirectToLogin.razor", "Properties/launchSettings.json", "wwwroot/appsettings.Development.json", "wwwroot/appsettings.json", + "wwwroot/icon-192.png", "wwwroot/index.html", "wwwroot/css/app.css" ] @@ -2266,18 +2524,18 @@ "Arguments": "new blazorwasm -au IndividualB2C", "Files": [ "Program.cs", - "Components/App.razor", - "Components/_Imports.razor", - "Components/Pages/Authentication.razor", - "Components/Pages/Counter.razor", - "Components/Pages/Home.razor", - "Components/Pages/Weather.razor", - "Components/Layout/MainLayout.razor", - "Components/Layout/MainLayout.razor.css", - "Components/Layout/NavMenu.razor", - "Components/Layout/NavMenu.razor.css", - "Components/Login/LoginDisplay.razor", - "Components/Login/RedirectToLogin.razor", + "App.razor", + "_Imports.razor", + "Pages/Authentication.razor", + "Pages/Counter.razor", + "Pages/Home.razor", + "Pages/Weather.razor", + "Layout/MainLayout.razor", + "Layout/MainLayout.razor.css", + "Layout/NavMenu.razor", + "Layout/NavMenu.razor.css", + "Layout/LoginDisplay.razor", + "Layout/RedirectToLogin.razor", "Properties/launchSettings.json", "wwwroot/appsettings.json", "wwwroot/favicon.png", @@ -2294,15 +2552,16 @@ "Arguments": "new blazorwasm -au IndividualB2C --empty", "Files": [ "Program.cs", - "Components/App.razor", - "Components/_Imports.razor", - "Components/Pages/Authentication.razor", - "Components/Pages/Home.razor", - "Components/Layout/MainLayout.razor", - "Components/Login/LoginDisplay.razor", - "Components/Login/RedirectToLogin.razor", + "App.razor", + "_Imports.razor", + "Pages/Authentication.razor", + "Pages/Home.razor", + "Layout/MainLayout.razor", + "Layout/LoginDisplay.razor", + "Layout/RedirectToLogin.razor", "Properties/launchSettings.json", "wwwroot/appsettings.json", + "wwwroot/icon-192.png", "wwwroot/index.html", "wwwroot/css/app.css" ] @@ -2312,18 +2571,18 @@ "Arguments": "new blazorwasm -au SingleOrg", "Files": [ "Program.cs", - "Components/App.razor", - "Components/_Imports.razor", - "Components/Pages/Authentication.razor", - "Components/Pages/Counter.razor", - "Components/Pages/Home.razor", - "Components/Pages/Weather.razor", - "Components/Layout/MainLayout.razor", - "Components/Layout/MainLayout.razor.css", - "Components/Layout/NavMenu.razor", - "Components/Layout/NavMenu.razor.css", - "Components/Login/LoginDisplay.razor", - "Components/Login/RedirectToLogin.razor", + "App.razor", + "_Imports.razor", + "Pages/Authentication.razor", + "Pages/Counter.razor", + "Pages/Home.razor", + "Pages/Weather.razor", + "Layout/MainLayout.razor", + "Layout/MainLayout.razor.css", + "Layout/NavMenu.razor", + "Layout/NavMenu.razor.css", + "Layout/LoginDisplay.razor", + "Layout/RedirectToLogin.razor", "Properties/launchSettings.json", "wwwroot/appsettings.json", "wwwroot/favicon.png", @@ -2340,15 +2599,16 @@ "Arguments": "new blazorwasm -au SingleOrg --empty", "Files": [ "Program.cs", - "Components/App.razor", - "Components/_Imports.razor", - "Components/Pages/Authentication.razor", - "Components/Pages/Home.razor", - "Components/Layout/MainLayout.razor", - "Components/Login/LoginDisplay.razor", - "Components/Login/RedirectToLogin.razor", + "App.razor", + "_Imports.razor", + "Pages/Authentication.razor", + "Pages/Home.razor", + "Layout/MainLayout.razor", + "Layout/LoginDisplay.razor", + "Layout/RedirectToLogin.razor", "Properties/launchSettings.json", "wwwroot/appsettings.json", + "wwwroot/icon-192.png", "wwwroot/index.html", "wwwroot/css/app.css" ] @@ -2358,15 +2618,15 @@ "Arguments": "new blazorwasm --pwa", "Files": [ "Program.cs", - "Components/App.razor", - "Components/_Imports.razor", - "Components/Pages/Counter.razor", - "Components/Pages/Home.razor", - "Components/Pages/Weather.razor", - "Components/Layout/MainLayout.razor", - "Components/Layout/MainLayout.razor.css", - "Components/Layout/NavMenu.razor", - "Components/Layout/NavMenu.razor.css", + "App.razor", + "_Imports.razor", + "Pages/Counter.razor", + "Pages/Home.razor", + "Pages/Weather.razor", + "Layout/MainLayout.razor", + "Layout/MainLayout.razor.css", + "Layout/NavMenu.razor", + "Layout/NavMenu.razor.css", "Properties/launchSettings.json", "wwwroot/favicon.png", "wwwroot/icon-192.png", @@ -2386,11 +2646,13 @@ "Arguments": "new blazorwasm --pwa --empty", "Files": [ "Program.cs", - "Components/App.razor", - "Components/_Imports.razor", - "Components/Pages/Home.razor", - "Components/Layout/MainLayout.razor", + "App.razor", + "_Imports.razor", + "Pages/Home.razor", + "Layout/MainLayout.razor", "Properties/launchSettings.json", + "wwwroot/icon-192.png", + "wwwroot/icon-512.png", "wwwroot/index.html", "wwwroot/manifest.webmanifest", "wwwroot/service-worker.js", @@ -2403,15 +2665,15 @@ "Arguments": "new blazorwasm --exclude-launch-settings", "Files": [ "Program.cs", - "Components/App.razor", - "Components/_Imports.razor", - "Components/Pages/Counter.razor", - "Components/Pages/Home.razor", - "Components/Pages/Weather.razor", - "Components/Layout/MainLayout.razor", - "Components/Layout/MainLayout.razor.css", - "Components/Layout/NavMenu.razor", - "Components/Layout/NavMenu.razor.css", + "App.razor", + "_Imports.razor", + "Pages/Counter.razor", + "Pages/Home.razor", + "Pages/Weather.razor", + "Layout/MainLayout.razor", + "Layout/MainLayout.razor.css", + "Layout/NavMenu.razor", + "Layout/NavMenu.razor.css", "wwwroot/favicon.png", "wwwroot/icon-192.png", "wwwroot/index.html", @@ -2426,10 +2688,11 @@ "Arguments": "new blazorwasm --empty --exclude-launch-settings", "Files": [ "Program.cs", - "Components/App.razor", - "Components/_Imports.razor", - "Components/Pages/Home.razor", - "Components/Layout/MainLayout.razor", + "App.razor", + "_Imports.razor", + "Pages/Home.razor", + "Layout/MainLayout.razor", + "wwwroot/icon-192.png", "wwwroot/index.html", "wwwroot/css/app.css" ] diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/TestInfrastructure/PrepareForTest.targets dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/TestInfrastructure/PrepareForTest.targets --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/TestInfrastructure/PrepareForTest.targets 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/TestInfrastructure/PrepareForTest.targets 2023-11-13 13:20:34.000000000 +0000 @@ -6,7 +6,7 @@ <_Parameter1>DotNetEfFullPath - <_Parameter2>$([MSBuild]::EnsureTrailingSlash('$(NuGetPackageRoot)'))dotnet-ef/$(DotnetEfVersion)/tools/net6.0/any/dotnet-ef.dll + <_Parameter2>$([MSBuild]::EnsureTrailingSlash('$(NuGetPackageRoot)'))dotnet-ef/$(DotnetEfVersion)/tools/net8.0/any/dotnet-ef.dll <_Parameter1>TestPackageRestorePath diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/THIRD-PARTY-NOTICES dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/THIRD-PARTY-NOTICES --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/THIRD-PARTY-NOTICES 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/THIRD-PARTY-NOTICES 2023-11-13 13:20:34.000000000 +0000 @@ -200,28 +200,3 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -License notice for Angular v8.0 --------------------------------------------- -The MIT License (MIT) -===================== - -Copyright (c) 2010-2019 Google LLC. http://angular.io/license - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/BlazorWeb-CSharp.csproj.in dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/BlazorWeb-CSharp.csproj.in --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/BlazorWeb-CSharp.csproj.in 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/BlazorWeb-CSharp.csproj.in 2023-11-13 13:20:34.000000000 +0000 @@ -12,7 +12,7 @@ - + Binary files /tmp/tmpzei_s1f4/vtUhdeu1_U/dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/app.db and /tmp/tmpzei_s1f4/QIlsu0xnyE/dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/app.db differ diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/appsettings.json dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/appsettings.json --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/appsettings.json 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/appsettings.json 2023-11-13 13:20:34.000000000 +0000 @@ -4,7 +4,7 @@ //#if (UseLocalDB) // "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-BlazorWeb_CSharp-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true" //#else -// "DefaultConnection": "DataSource=app.db;Cache=Shared" +// "DefaultConnection": "DataSource=Data\\app.db;Cache=Shared" //#endif // }, ////#endif diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,112 @@ +using System.Security.Claims; +using System.Text.Json; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Primitives; +using BlazorWeb_CSharp.Components.Account.Pages; +using BlazorWeb_CSharp.Components.Account.Pages.Manage; +using BlazorWeb_CSharp.Data; + +namespace Microsoft.AspNetCore.Routing; + +internal static class IdentityComponentsEndpointRouteBuilderExtensions +{ + // These endpoints are required by the Identity Razor components defined in the /Components/Account/Pages directory of this project. + public static IEndpointConventionBuilder MapAdditionalIdentityEndpoints(this IEndpointRouteBuilder endpoints) + { + ArgumentNullException.ThrowIfNull(endpoints); + + var accountGroup = endpoints.MapGroup("/Account"); + + accountGroup.MapPost("/PerformExternalLogin", ( + HttpContext context, + [FromServices] SignInManager signInManager, + [FromForm] string provider, + [FromForm] string returnUrl) => + { + IEnumerable> query = [ + new("ReturnUrl", returnUrl), + new("Action", ExternalLogin.LoginCallbackAction)]; + + var redirectUrl = UriHelper.BuildRelative( + context.Request.PathBase, + "/Account/ExternalLogin", + QueryString.Create(query)); + + var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + return TypedResults.Challenge(properties, [provider]); + }); + + accountGroup.MapPost("/Logout", async ( + ClaimsPrincipal user, + SignInManager signInManager, + [FromForm] string returnUrl) => + { + await signInManager.SignOutAsync(); + return TypedResults.LocalRedirect($"~/{returnUrl}"); + }); + + var manageGroup = accountGroup.MapGroup("/Manage").RequireAuthorization(); + + manageGroup.MapPost("/LinkExternalLogin", async ( + HttpContext context, + [FromServices] SignInManager signInManager, + [FromForm] string provider) => + { + // Clear the existing external cookie to ensure a clean login process + await context.SignOutAsync(IdentityConstants.ExternalScheme); + + var redirectUrl = UriHelper.BuildRelative( + context.Request.PathBase, + "/Account/Manage/ExternalLogins", + QueryString.Create("Action", ExternalLogins.LinkLoginCallbackAction)); + + var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, signInManager.UserManager.GetUserId(context.User)); + return TypedResults.Challenge(properties, [provider]); + }); + + var loggerFactory = endpoints.ServiceProvider.GetRequiredService(); + var downloadLogger = loggerFactory.CreateLogger("DownloadPersonalData"); + + manageGroup.MapPost("/DownloadPersonalData", async ( + HttpContext context, + [FromServices] UserManager userManager, + [FromServices] AuthenticationStateProvider authenticationStateProvider) => + { + var user = await userManager.GetUserAsync(context.User); + if (user is null) + { + return Results.NotFound($"Unable to load user with ID '{userManager.GetUserId(context.User)}'."); + } + + var userId = await userManager.GetUserIdAsync(user); + downloadLogger.LogInformation("User with ID '{UserId}' asked for their personal data.", userId); + + // Only include personal data for download + var personalData = new Dictionary(); + var personalDataProps = typeof(ApplicationUser).GetProperties().Where( + prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute))); + foreach (var p in personalDataProps) + { + personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null"); + } + + var logins = await userManager.GetLoginsAsync(user); + foreach (var l in logins) + { + personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey); + } + + personalData.Add("Authenticator Key", (await userManager.GetAuthenticatorKeyAsync(user))!); + var fileBytes = JsonSerializer.SerializeToUtf8Bytes(personalData); + + context.Response.Headers.TryAdd("Content-Disposition", "attachment; filename=PersonalData.json"); + return TypedResults.File(fileBytes, contentType: "application/json", fileDownloadName: "PersonalData.json"); + }); + + return accountGroup; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityNoOpEmailSender.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityNoOpEmailSender.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityNoOpEmailSender.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityNoOpEmailSender.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI.Services; +using BlazorWeb_CSharp.Data; + +namespace BlazorWeb_CSharp.Components.Account; + +// Remove the "else if (EmailSender is IdentityNoOpEmailSender)" block from RegisterConfirmation.razor after updating with a real implementation. +internal sealed class IdentityNoOpEmailSender : IEmailSender +{ + private readonly IEmailSender emailSender = new NoOpEmailSender(); + + public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) => + emailSender.SendEmailAsync(email, "Confirm your email", $"Please confirm your account by clicking here."); + + public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) => + emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password by clicking here."); + + public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) => + emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password using the following code: {resetCode}"); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,58 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Components; + +namespace BlazorWeb_CSharp.Components.Account; + +internal sealed class IdentityRedirectManager(NavigationManager navigationManager) +{ + public const string StatusCookieName = "Identity.StatusMessage"; + + private static readonly CookieBuilder StatusCookieBuilder = new() + { + SameSite = SameSiteMode.Strict, + HttpOnly = true, + IsEssential = true, + MaxAge = TimeSpan.FromSeconds(5), + }; + + [DoesNotReturn] + public void RedirectTo(string? uri) + { + uri ??= ""; + + // Prevent open redirects. + if (!Uri.IsWellFormedUriString(uri, UriKind.Relative)) + { + uri = navigationManager.ToBaseRelativePath(uri); + } + + // During static rendering, NavigateTo throws a NavigationException which is handled by the framework as a redirect. + // So as long as this is called from a statically rendered Identity component, the InvalidOperationException is never thrown. + navigationManager.NavigateTo(uri); + throw new InvalidOperationException($"{nameof(IdentityRedirectManager)} can only be used during static rendering."); + } + + [DoesNotReturn] + public void RedirectTo(string uri, Dictionary queryParameters) + { + var uriWithoutQuery = navigationManager.ToAbsoluteUri(uri).GetLeftPart(UriPartial.Path); + var newUri = navigationManager.GetUriWithQueryParameters(uriWithoutQuery, queryParameters); + RedirectTo(newUri); + } + + [DoesNotReturn] + public void RedirectToWithStatus(string uri, string message, HttpContext context) + { + context.Response.Cookies.Append(StatusCookieName, message, StatusCookieBuilder.Build(context)); + RedirectTo(uri); + } + + private string CurrentPath => navigationManager.ToAbsoluteUri(navigationManager.Uri).GetLeftPart(UriPartial.Path); + + [DoesNotReturn] + public void RedirectToCurrentPage() => RedirectTo(CurrentPath); + + [DoesNotReturn] + public void RedirectToCurrentPageWithStatus(string message, HttpContext context) + => RedirectToWithStatus(CurrentPath, message, context); +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,47 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.Server; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using BlazorWeb_CSharp.Data; + +namespace BlazorWeb_CSharp.Components.Account; + +// This is a server-side AuthenticationStateProvider that revalidates the security stamp for the connected user +// every 30 minutes an interactive circuit is connected. +internal sealed class IdentityRevalidatingAuthenticationStateProvider( + ILoggerFactory loggerFactory, + IServiceScopeFactory scopeFactory, + IOptions options) + : RevalidatingServerAuthenticationStateProvider(loggerFactory) +{ + protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30); + + protected override async Task ValidateAuthenticationStateAsync( + AuthenticationState authenticationState, CancellationToken cancellationToken) + { + // Get the user manager from a new scope to ensure it fetches fresh data + await using var scope = scopeFactory.CreateAsyncScope(); + var userManager = scope.ServiceProvider.GetRequiredService>(); + return await ValidateSecurityStampAsync(userManager, authenticationState.User); + } + + private async Task ValidateSecurityStampAsync(UserManager userManager, ClaimsPrincipal principal) + { + var user = await userManager.GetUserAsync(principal); + if (user is null) + { + return false; + } + else if (!userManager.SupportsUserSecurityStamp) + { + return true; + } + else + { + var principalStamp = principal.FindFirstValue(options.Value.ClaimsIdentity.SecurityStampClaimType); + var userStamp = await userManager.GetSecurityStampAsync(user); + return principalStamp == userStamp; + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityUserAccessor.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityUserAccessor.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityUserAccessor.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityUserAccessor.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Identity; +using BlazorWeb_CSharp.Data; + +namespace BlazorWeb_CSharp.Components.Account; + +internal sealed class IdentityUserAccessor(UserManager userManager, IdentityRedirectManager redirectManager) +{ + public async Task GetRequiredUserAsync(HttpContext context) + { + var user = await userManager.GetUserAsync(context.User); + + if (user is null) + { + redirectManager.RedirectToWithStatus("Account/InvalidUser", $"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context); + } + + return user; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmailChange.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmailChange.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmailChange.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmailChange.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,68 @@ +@page "/Account/ConfirmEmailChange" + +@using System.Text +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityRedirectManager RedirectManager + +Confirm email change + +

    Confirm email change

    + + + +@code { + private string? message; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromQuery] + private string? UserId { get; set; } + + [SupplyParameterFromQuery] + private string? Email { get; set; } + + [SupplyParameterFromQuery] + private string? Code { get; set; } + + protected override async Task OnInitializedAsync() + { + if (UserId is null || Email is null || Code is null) + { + RedirectManager.RedirectToWithStatus( + "Account/Login", "Error: Invalid email change confirmation link.", HttpContext); + } + + var user = await UserManager.FindByIdAsync(UserId); + if (user is null) + { + message = "Unable to find user with Id '{userId}'"; + return; + } + + var code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code)); + var result = await UserManager.ChangeEmailAsync(user, Email, code); + if (!result.Succeeded) + { + message = "Error changing email."; + return; + } + + // In our UI email and user name are one and the same, so when we update the email + // we need to update the user name. + var setUserNameResult = await UserManager.SetUserNameAsync(user, Email); + if (!setUserNameResult.Succeeded) + { + message = "Error changing user name."; + return; + } + + await SignInManager.RefreshSignInAsync(user); + message = "Thank you for confirming your email change."; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmail.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmail.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmail.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmail.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,48 @@ +@page "/Account/ConfirmEmail" + +@using System.Text +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject IdentityRedirectManager RedirectManager + +Confirm email + +

    Confirm email

    + + +@code { + private string? statusMessage; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromQuery] + private string? UserId { get; set; } + + [SupplyParameterFromQuery] + private string? Code { get; set; } + + protected override async Task OnInitializedAsync() + { + if (UserId is null || Code is null) + { + RedirectManager.RedirectTo(""); + } + + var user = await UserManager.FindByIdAsync(UserId); + if (user is null) + { + HttpContext.Response.StatusCode = StatusCodes.Status404NotFound; + statusMessage = $"Error loading user with ID {UserId}"; + } + else + { + var code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code)); + var result = await UserManager.ConfirmEmailAsync(user, code); + statusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email."; + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ExternalLogin.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ExternalLogin.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ExternalLogin.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ExternalLogin.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,195 @@ +@page "/Account/ExternalLogin" + +@using System.ComponentModel.DataAnnotations +@using System.Security.Claims +@using System.Text +@using System.Text.Encodings.Web +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorWeb_CSharp.Data + +@inject SignInManager SignInManager +@inject UserManager UserManager +@inject IUserStore UserStore +@inject IEmailSender EmailSender +@inject NavigationManager NavigationManager +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Register + + +

    Register

    +

    Associate your @ProviderDisplayName account.

    +
    + +
    + You've successfully authenticated with @ProviderDisplayName. + Please enter an email address for this site below and click the Register button to finish + logging in. +
    + +
    +
    + + + +
    + + + +
    + +
    +
    +
    + +@code { + public const string LoginCallbackAction = "LoginCallback"; + + private string? message; + private ExternalLoginInfo externalLoginInfo = default!; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = new(); + + [SupplyParameterFromQuery] + private string? RemoteError { get; set; } + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + [SupplyParameterFromQuery] + private string? Action { get; set; } + + private string? ProviderDisplayName => externalLoginInfo.ProviderDisplayName; + + protected override async Task OnInitializedAsync() + { + if (RemoteError is not null) + { + RedirectManager.RedirectToWithStatus("Account/Login", $"Error from external provider: {RemoteError}", HttpContext); + } + + var info = await SignInManager.GetExternalLoginInfoAsync(); + if (info is null) + { + RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information.", HttpContext); + } + + externalLoginInfo = info; + + if (HttpMethods.IsGet(HttpContext.Request.Method)) + { + if (Action == LoginCallbackAction) + { + await OnLoginCallbackAsync(); + return; + } + + // We should only reach this page via the login callback, so redirect back to + // the login page if we get here some other way. + RedirectManager.RedirectTo("Account/Login"); + } + } + + private async Task OnLoginCallbackAsync() + { + // Sign in the user with this external login provider if the user already has a login. + var result = await SignInManager.ExternalLoginSignInAsync( + externalLoginInfo.LoginProvider, + externalLoginInfo.ProviderKey, + isPersistent: false, + bypassTwoFactor: true); + + if (result.Succeeded) + { + Logger.LogInformation( + "{Name} logged in with {LoginProvider} provider.", + externalLoginInfo.Principal.Identity?.Name, + externalLoginInfo.LoginProvider); + RedirectManager.RedirectTo(ReturnUrl); + } + else if (result.IsLockedOut) + { + RedirectManager.RedirectTo("Account/Lockout"); + } + + // If the user does not have an account, then ask the user to create an account. + if (externalLoginInfo.Principal.HasClaim(c => c.Type == ClaimTypes.Email)) + { + Input.Email = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? ""; + } + } + + private async Task OnValidSubmitAsync() + { + var emailStore = GetEmailStore(); + var user = CreateUser(); + + await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); + await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); + + var result = await UserManager.CreateAsync(user); + if (result.Succeeded) + { + result = await UserManager.AddLoginAsync(user, externalLoginInfo); + if (result.Succeeded) + { + Logger.LogInformation("User created an account using {Name} provider.", externalLoginInfo.LoginProvider); + + var userId = await UserManager.GetUserIdAsync(user); + var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + + var callbackUrl = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, + new Dictionary { ["userId"] = userId, ["code"] = code }); + await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl)); + + // If account confirmation is required, we need to show the link if we don't have a real email sender + if (UserManager.Options.SignIn.RequireConfirmedAccount) + { + RedirectManager.RedirectTo("Account/RegisterConfirmation", new() { ["email"] = Input.Email }); + } + + await SignInManager.SignInAsync(user, isPersistent: false, externalLoginInfo.LoginProvider); + RedirectManager.RedirectTo(ReturnUrl); + } + } + + message = $"Error: {string.Join(",", result.Errors.Select(error => error.Description))}"; + } + + private ApplicationUser CreateUser() + { + try + { + return Activator.CreateInstance(); + } + catch + { + throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " + + $"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor"); + } + } + + private IUserEmailStore GetEmailStore() + { + if (!UserManager.SupportsUserEmail) + { + throw new NotSupportedException("The default UI requires a user store with email support."); + } + return (IUserEmailStore)UserStore; + } + + private sealed class InputModel + { + [Required] + [EmailAddress] + public string Email { get; set; } = ""; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ForgotPasswordConfirmation.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ForgotPasswordConfirmation.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ForgotPasswordConfirmation.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ForgotPasswordConfirmation.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +@page "/Account/ForgotPasswordConfirmation" + +Forgot password confirmation + +

    Forgot password confirmation

    +

    + Please check your email to reset your password. +

    diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ForgotPassword.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ForgotPassword.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ForgotPassword.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ForgotPassword.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,68 @@ +@page "/Account/ForgotPassword" + +@using System.ComponentModel.DataAnnotations +@using System.Text +@using System.Text.Encodings.Web +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject IEmailSender EmailSender +@inject NavigationManager NavigationManager +@inject IdentityRedirectManager RedirectManager + +Forgot your password? + +

    Forgot your password?

    +

    Enter your email.

    +
    +
    +
    + + + + +
    + + + +
    + +
    +
    +
    + +@code { + [SupplyParameterFromForm] + private InputModel Input { get; set; } = new(); + + private async Task OnValidSubmitAsync() + { + var user = await UserManager.FindByEmailAsync(Input.Email); + if (user is null || !(await UserManager.IsEmailConfirmedAsync(user))) + { + // Don't reveal that the user does not exist or is not confirmed + RedirectManager.RedirectTo("Account/ForgotPasswordConfirmation"); + } + + // For more information on how to enable account confirmation and password reset please + // visit https://go.microsoft.com/fwlink/?LinkID=532713 + var code = await UserManager.GeneratePasswordResetTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ResetPassword").AbsoluteUri, + new Dictionary { ["code"] = code }); + + await EmailSender.SendPasswordResetLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl)); + + RedirectManager.RedirectTo("Account/ForgotPasswordConfirmation"); + } + + private sealed class InputModel + { + [Required] + [EmailAddress] + public string Email { get; set; } = ""; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/_Imports.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/_Imports.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/_Imports.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/_Imports.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,2 @@ +@using BlazorWeb_CSharp.Components.Account.Shared +@layout AccountLayout diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/InvalidPasswordReset.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/InvalidPasswordReset.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/InvalidPasswordReset.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/InvalidPasswordReset.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +@page "/Account/InvalidPasswordReset" + +Invalid password reset + +

    Invalid password reset

    +

    + The password reset link is invalid. +

    diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/InvalidUser.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/InvalidUser.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/InvalidUser.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/InvalidUser.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,7 @@ +@page "/Account/InvalidUser" + +Invalid user + +

    Invalid user

    + + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Lockout.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Lockout.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Lockout.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Lockout.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +@page "/Account/Lockout" + +Locked out + +
    +

    Locked out

    +

    This account has been locked out, please try again later.

    +
    diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Login.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Login.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Login.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Login.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,128 @@ +@page "/Account/Login" + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Authentication +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject SignInManager SignInManager +@inject ILogger Logger +@inject NavigationManager NavigationManager +@inject IdentityRedirectManager RedirectManager + +Log in + +

    Log in

    +
    +
    +
    + + + +

    Use a local account to log in.

    +
    + +
    + + + +
    +
    + + + +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    +

    Use another service to log in.

    +
    + +
    +
    +
    + +@code { + private string? errorMessage; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = new(); + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + protected override async Task OnInitializedAsync() + { + if (HttpMethods.IsGet(HttpContext.Request.Method)) + { + // Clear the existing external cookie to ensure a clean login process + await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); + } + } + + public async Task LoginUser() + { + // This doesn't count login failures towards account lockout + // To enable password failures to trigger account lockout, set lockoutOnFailure: true + var result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); + if (result.Succeeded) + { + Logger.LogInformation("User logged in."); + RedirectManager.RedirectTo(ReturnUrl); + } + else if (result.RequiresTwoFactor) + { + RedirectManager.RedirectTo( + "Account/LoginWith2fa", + new() { ["returnUrl"] = ReturnUrl, ["rememberMe"] = Input.RememberMe }); + } + else if (result.IsLockedOut) + { + Logger.LogWarning("User account locked out."); + RedirectManager.RedirectTo("Account/Lockout"); + } + else + { + errorMessage = "Error: Invalid login attempt."; + } + } + + private sealed class InputModel + { + [Required] + [EmailAddress] + public string Email { get; set; } = ""; + + [Required] + [DataType(DataType.Password)] + public string Password { get; set; } = ""; + + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/LoginWith2fa.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/LoginWith2fa.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/LoginWith2fa.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/LoginWith2fa.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,101 @@ +@page "/Account/LoginWith2fa" + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject SignInManager SignInManager +@inject UserManager UserManager +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Two-factor authentication + +

    Two-factor authentication

    +
    + +

    Your login is protected with an authenticator app. Enter your authenticator code below.

    +
    +
    + + + + + +
    + + + +
    +
    + +
    +
    + +
    +
    +
    +
    +

    + Don't have access to your authenticator device? You can + log in with a recovery code. +

    + +@code { + private string? message; + private ApplicationUser user = default!; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = new(); + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + [SupplyParameterFromQuery] + private bool RememberMe { get; set; } + + protected override async Task OnInitializedAsync() + { + // Ensure the user has gone through the username & password screen first + user = await SignInManager.GetTwoFactorAuthenticationUserAsync() ?? + throw new InvalidOperationException("Unable to load two-factor authentication user."); + } + + private async Task OnValidSubmitAsync() + { + var authenticatorCode = Input.TwoFactorCode!.Replace(" ", string.Empty).Replace("-", string.Empty); + var result = await SignInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, RememberMe, Input.RememberMachine); + var userId = await UserManager.GetUserIdAsync(user); + + if (result.Succeeded) + { + Logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", userId); + RedirectManager.RedirectTo(ReturnUrl); + } + else if (result.IsLockedOut) + { + Logger.LogWarning("User with ID '{UserId}' account locked out.", userId); + RedirectManager.RedirectTo("Account/Lockout"); + } + else + { + Logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", userId); + message = "Error: Invalid authenticator code."; + } + } + + private sealed class InputModel + { + [Required] + [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Text)] + [Display(Name = "Authenticator code")] + public string? TwoFactorCode { get; set; } + + [Display(Name = "Remember this machine")] + public bool RememberMachine { get; set; } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/LoginWithRecoveryCode.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/LoginWithRecoveryCode.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/LoginWithRecoveryCode.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/LoginWithRecoveryCode.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,85 @@ +@page "/Account/LoginWithRecoveryCode" + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject SignInManager SignInManager +@inject UserManager UserManager +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Recovery code verification + +

    Recovery code verification

    +
    + +

    + You have requested to log in with a recovery code. This login will not be remembered until you provide + an authenticator app code at log in or disable 2FA and log in again. +

    +
    +
    + + + +
    + + + +
    + +
    +
    +
    + +@code { + private string? message; + private ApplicationUser user = default!; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = new(); + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + protected override async Task OnInitializedAsync() + { + // Ensure the user has gone through the username & password screen first + user = await SignInManager.GetTwoFactorAuthenticationUserAsync() ?? + throw new InvalidOperationException("Unable to load two-factor authentication user."); + } + + private async Task OnValidSubmitAsync() + { + var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty); + + var result = await SignInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode); + + var userId = await UserManager.GetUserIdAsync(user); + + if (result.Succeeded) + { + Logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", userId); + RedirectManager.RedirectTo(ReturnUrl); + } + else if (result.IsLockedOut) + { + Logger.LogWarning("User account locked out."); + RedirectManager.RedirectTo("Account/Lockout"); + } + else + { + Logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", userId); + message = "Error: Invalid recovery code entered."; + } + } + + private sealed class InputModel + { + [Required] + [DataType(DataType.Text)] + [Display(Name = "Recovery Code")] + public string RecoveryCode { get; set; } = ""; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,96 @@ +@page "/Account/Manage/ChangePassword" + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Change password + +

    Change password

    + +
    +
    + + + +
    + + + +
    +
    + + + +
    +
    + + + +
    + +
    +
    +
    + +@code { + private string? message; + private ApplicationUser user = default!; + private bool hasPassword; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + hasPassword = await UserManager.HasPasswordAsync(user); + if (!hasPassword) + { + RedirectManager.RedirectTo("Account/Manage/SetPassword"); + } + } + + private async Task OnValidSubmitAsync() + { + var changePasswordResult = await UserManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword); + if (!changePasswordResult.Succeeded) + { + message = $"Error: {string.Join(",", changePasswordResult.Errors.Select(error => error.Description))}"; + return; + } + + await SignInManager.RefreshSignInAsync(user); + Logger.LogInformation("User changed their password successfully."); + + RedirectManager.RedirectToCurrentPageWithStatus("Your password has been changed", HttpContext); + } + + private sealed class InputModel + { + [Required] + [DataType(DataType.Password)] + [Display(Name = "Current password")] + public string OldPassword { get; set; } = ""; + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string NewPassword { get; set; } = ""; + + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } = ""; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/DeletePersonalData.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/DeletePersonalData.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/DeletePersonalData.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/DeletePersonalData.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,86 @@ +@page "/Account/Manage/DeletePersonalData" + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Delete Personal Data + + + +

    Delete Personal Data

    + + + +
    + + + + @if (requirePassword) + { +
    + + + +
    + } + +
    +
    + +@code { + private string? message; + private ApplicationUser user = default!; + private bool requirePassword; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + Input ??= new(); + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + requirePassword = await UserManager.HasPasswordAsync(user); + } + + private async Task OnValidSubmitAsync() + { + if (requirePassword && !await UserManager.CheckPasswordAsync(user, Input.Password)) + { + message = "Error: Incorrect password."; + return; + } + + var result = await UserManager.DeleteAsync(user); + if (!result.Succeeded) + { + throw new InvalidOperationException("Unexpected error occurred deleting user."); + } + + await SignInManager.SignOutAsync(); + + var userId = await UserManager.GetUserIdAsync(user); + Logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId); + + RedirectManager.RedirectToCurrentPage(); + } + + private sealed class InputModel + { + [DataType(DataType.Password)] + public string Password { get; set; } = ""; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,64 @@ +@page "/Account/Manage/Disable2fa" + +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Disable two-factor authentication (2FA) + + +

    Disable two-factor authentication (2FA)

    + + + +
    + + + + +
    + +@code { + private ApplicationUser user = default!; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + protected override async Task OnInitializedAsync() + { + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + + if (HttpMethods.IsGet(HttpContext.Request.Method) && !await UserManager.GetTwoFactorEnabledAsync(user)) + { + throw new InvalidOperationException("Cannot disable 2FA for user as it's not currently enabled."); + } + } + + private async Task OnSubmitAsync() + { + var disable2faResult = await UserManager.SetTwoFactorEnabledAsync(user, false); + if (!disable2faResult.Succeeded) + { + throw new InvalidOperationException("Unexpected error occurred disabling 2FA."); + } + + var userId = await UserManager.GetUserIdAsync(user); + Logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", userId); + RedirectManager.RedirectToWithStatus( + "Account/Manage/TwoFactorAuthentication", + "2fa has been disabled. You can reenable 2fa when you setup an authenticator app", + HttpContext); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,123 @@ +@page "/Account/Manage/Email" + +@using System.ComponentModel.DataAnnotations +@using System.Text +@using System.Text.Encodings.Web +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject IEmailSender EmailSender +@inject IdentityUserAccessor UserAccessor +@inject NavigationManager NavigationManager + +Manage email + +

    Manage email

    + + +
    +
    +
    + + + + + + @if (isEmailConfirmed) + { +
    + +
    + +
    + +
    + } + else + { +
    + + + +
    + } +
    + + + +
    + +
    +
    +
    + +@code { + private string? message; + private ApplicationUser user = default!; + private string? email; + private bool isEmailConfirmed; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm(FormName = "change-email")] + private InputModel Input { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + email = await UserManager.GetEmailAsync(user); + isEmailConfirmed = await UserManager.IsEmailConfirmedAsync(user); + + Input.NewEmail ??= email; + } + + private async Task OnValidSubmitAsync() + { + if (Input.NewEmail is null || Input.NewEmail == email) + { + message = "Your email is unchanged."; + return; + } + + var userId = await UserManager.GetUserIdAsync(user); + var code = await UserManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ConfirmEmailChange").AbsoluteUri, + new Dictionary { ["userId"] = userId, ["email"] = Input.NewEmail, ["code"] = code }); + + await EmailSender.SendConfirmationLinkAsync(user, Input.NewEmail, HtmlEncoder.Default.Encode(callbackUrl)); + + message = "Confirmation link to change email sent. Please check your email."; + } + + private async Task OnSendEmailVerificationAsync() + { + if (email is null) + { + return; + } + + var userId = await UserManager.GetUserIdAsync(user); + var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, + new Dictionary { ["userId"] = userId, ["code"] = code }); + + await EmailSender.SendConfirmationLinkAsync(user, email, HtmlEncoder.Default.Encode(callbackUrl)); + + message = "Verification email sent. Please check your email."; + } + + private sealed class InputModel + { + [Required] + [EmailAddress] + [Display(Name = "New email")] + public string? NewEmail { get; set; } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/EnableAuthenticator.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/EnableAuthenticator.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/EnableAuthenticator.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/EnableAuthenticator.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,172 @@ +@page "/Account/Manage/EnableAuthenticator" + +@using System.ComponentModel.DataAnnotations +@using System.Globalization +@using System.Text +@using System.Text.Encodings.Web +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject IdentityUserAccessor UserAccessor +@inject UrlEncoder UrlEncoder +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Configure authenticator app + +@if (recoveryCodes is not null) +{ + +} +else +{ + +

    Configure authenticator app

    +
    +

    To use an authenticator app go through the following steps:

    +
      +
    1. +

      + Download a two-factor authenticator app like Microsoft Authenticator for + Android and + iOS or + Google Authenticator for + Android and + iOS. +

      +
    2. +
    3. +

      Scan the QR Code or enter this key @sharedKey into your two factor authenticator app. Spaces and casing do not matter.

      + +
      +
      +
    4. +
    5. +

      + Once you have scanned the QR code or input the key above, your two factor authentication app will provide you + with a unique code. Enter the code in the confirmation box below. +

      +
      +
      + + +
      + + + +
      + + +
      +
      +
      +
    6. +
    +
    +} + +@code { + private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6"; + + private string? message; + private ApplicationUser user = default!; + private string? sharedKey; + private string? authenticatorUri; + private IEnumerable? recoveryCodes; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + + await LoadSharedKeyAndQrCodeUriAsync(user); + } + + private async Task OnValidSubmitAsync() + { + // Strip spaces and hyphens + var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty); + + var is2faTokenValid = await UserManager.VerifyTwoFactorTokenAsync( + user, UserManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); + + if (!is2faTokenValid) + { + message = "Error: Verification code is invalid."; + return; + } + + await UserManager.SetTwoFactorEnabledAsync(user, true); + var userId = await UserManager.GetUserIdAsync(user); + Logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId); + + message = "Your authenticator app has been verified."; + + if (await UserManager.CountRecoveryCodesAsync(user) == 0) + { + recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); + } + else + { + RedirectManager.RedirectToWithStatus("Account/Manage/TwoFactorAuthentication", message, HttpContext); + } + } + + private async ValueTask LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user) + { + // Load the authenticator key & QR code URI to display on the form + var unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user); + if (string.IsNullOrEmpty(unformattedKey)) + { + await UserManager.ResetAuthenticatorKeyAsync(user); + unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user); + } + + sharedKey = FormatKey(unformattedKey!); + + var email = await UserManager.GetEmailAsync(user); + authenticatorUri = GenerateQrCodeUri(email!, unformattedKey!); + } + + private string FormatKey(string unformattedKey) + { + var result = new StringBuilder(); + int currentPosition = 0; + while (currentPosition + 4 < unformattedKey.Length) + { + result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' '); + currentPosition += 4; + } + if (currentPosition < unformattedKey.Length) + { + result.Append(unformattedKey.AsSpan(currentPosition)); + } + + return result.ToString().ToLowerInvariant(); + } + + private string GenerateQrCodeUri(string email, string unformattedKey) + { + return string.Format( + CultureInfo.InvariantCulture, + AuthenticatorUriFormat, + UrlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"), + UrlEncoder.Encode(email), + unformattedKey); + } + + private sealed class InputModel + { + [Required] + [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Text)] + [Display(Name = "Verification Code")] + public string Code { get; set; } = ""; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ExternalLogins.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ExternalLogins.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ExternalLogins.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ExternalLogins.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,140 @@ +@page "/Account/Manage/ExternalLogins" + +@using Microsoft.AspNetCore.Authentication +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityUserAccessor UserAccessor +@inject IUserStore UserStore +@inject IdentityRedirectManager RedirectManager + +Manage your external logins + + +@if (currentLogins?.Count > 0) +{ +

    Registered Logins

    + + + @foreach (var login in currentLogins) + { + + + + + } + +
    @login.ProviderDisplayName + @if (showRemoveButton) + { +
    + +
    + + + +
    + + } + else + { + @:   + } +
    +} +@if (otherLogins?.Count > 0) +{ +

    Add another service to log in.

    +
    +
    + +
    +

    + @foreach (var provider in otherLogins) + { + + } +

    +
    + +} + +@code { + public const string LinkLoginCallbackAction = "LinkLoginCallback"; + + private ApplicationUser user = default!; + private IList? currentLogins; + private IList? otherLogins; + private bool showRemoveButton; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private string? LoginProvider { get; set; } + + [SupplyParameterFromForm] + private string? ProviderKey { get; set; } + + [SupplyParameterFromQuery] + private string? Action { get; set; } + + protected override async Task OnInitializedAsync() + { + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + currentLogins = await UserManager.GetLoginsAsync(user); + otherLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()) + .Where(auth => currentLogins.All(ul => auth.Name != ul.LoginProvider)) + .ToList(); + + string? passwordHash = null; + if (UserStore is IUserPasswordStore userPasswordStore) + { + passwordHash = await userPasswordStore.GetPasswordHashAsync(user, HttpContext.RequestAborted); + } + + showRemoveButton = passwordHash is not null || currentLogins.Count > 1; + + if (HttpMethods.IsGet(HttpContext.Request.Method) && Action == LinkLoginCallbackAction) + { + await OnGetLinkLoginCallbackAsync(); + } + } + + private async Task OnSubmitAsync() + { + var result = await UserManager.RemoveLoginAsync(user, LoginProvider!, ProviderKey!); + if (!result.Succeeded) + { + RedirectManager.RedirectToCurrentPageWithStatus("Error: The external login was not removed.", HttpContext); + } + + await SignInManager.RefreshSignInAsync(user); + RedirectManager.RedirectToCurrentPageWithStatus("The external login was removed.", HttpContext); + } + + private async Task OnGetLinkLoginCallbackAsync() + { + var userId = await UserManager.GetUserIdAsync(user); + var info = await SignInManager.GetExternalLoginInfoAsync(userId); + if (info is null) + { + RedirectManager.RedirectToCurrentPageWithStatus("Error: Could not load external login info.", HttpContext); + } + + var result = await UserManager.AddLoginAsync(user, info); + if (!result.Succeeded) + { + RedirectManager.RedirectToCurrentPageWithStatus("Error: The external login was not added. External logins can only be associated with one account.", HttpContext); + } + + // Clear the existing external cookie to ensure a clean login process + await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); + + RedirectManager.RedirectToCurrentPageWithStatus("The external login was added.", HttpContext); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,68 @@ +@page "/Account/Manage/GenerateRecoveryCodes" + +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Generate two-factor authentication (2FA) recovery codes + +@if (recoveryCodes is not null) +{ + +} +else +{ +

    Generate two-factor authentication (2FA) recovery codes

    + +
    +
    + + + +
    +} + +@code { + private string? message; + private ApplicationUser user = default!; + private IEnumerable? recoveryCodes; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + protected override async Task OnInitializedAsync() + { + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + + var isTwoFactorEnabled = await UserManager.GetTwoFactorEnabledAsync(user); + if (!isTwoFactorEnabled) + { + throw new InvalidOperationException("Cannot generate recovery codes for user because they do not have 2FA enabled."); + } + } + + private async Task OnSubmitAsync() + { + var userId = await UserManager.GetUserIdAsync(user); + recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); + message = "You have generated new recovery codes."; + + Logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/_Imports.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/_Imports.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/_Imports.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/_Imports.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,2 @@ +@layout ManageLayout +@attribute [Microsoft.AspNetCore.Authorization.Authorize] diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Index.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Index.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Index.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Index.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,77 @@ +@page "/Account/Manage" + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager + +Profile + +

    Profile

    + + +
    +
    + + + +
    + + +
    +
    + + + +
    + +
    +
    +
    + +@code { + private ApplicationUser user = default!; + private string? username; + private string? phoneNumber; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + username = await UserManager.GetUserNameAsync(user); + phoneNumber = await UserManager.GetPhoneNumberAsync(user); + + Input.PhoneNumber ??= phoneNumber; + } + + private async Task OnValidSubmitAsync() + { + if (Input.PhoneNumber != phoneNumber) + { + var setPhoneResult = await UserManager.SetPhoneNumberAsync(user, Input.PhoneNumber); + if (!setPhoneResult.Succeeded) + { + RedirectManager.RedirectToCurrentPageWithStatus("Error: Failed to set phone number.", HttpContext); + } + } + + await SignInManager.RefreshSignInAsync(user); + RedirectManager.RedirectToCurrentPageWithStatus("Your profile has been updated", HttpContext); + } + + private sealed class InputModel + { + [Phone] + [Display(Name = "Phone number")] + public string? PhoneNumber { get; set; } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/PersonalData.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/PersonalData.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/PersonalData.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/PersonalData.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,34 @@ +@page "/Account/Manage/PersonalData" + +@inject IdentityUserAccessor UserAccessor + +Personal Data + + +

    Personal Data

    + +
    +
    +

    Your account contains personal data that you have given us. This page allows you to download or delete that data.

    +

    + Deleting this data will permanently remove your account, and this cannot be recovered. +

    +
    + + + +

    + Delete +

    +
    +
    + +@code { + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + protected override async Task OnInitializedAsync() + { + _ = await UserAccessor.GetRequiredUserAsync(HttpContext); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ResetAuthenticator.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ResetAuthenticator.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ResetAuthenticator.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ResetAuthenticator.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,52 @@ +@page "/Account/Manage/ResetAuthenticator" + +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Reset authenticator key + + +

    Reset authenticator key

    + +
    +
    + + + +
    + +@code { + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + private async Task OnSubmitAsync() + { + var user = await UserAccessor.GetRequiredUserAsync(HttpContext); + await UserManager.SetTwoFactorEnabledAsync(user, false); + await UserManager.ResetAuthenticatorKeyAsync(user); + var userId = await UserManager.GetUserIdAsync(user); + Logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", userId); + + await SignInManager.RefreshSignInAsync(user); + + RedirectManager.RedirectToWithStatus( + "Account/Manage/EnableAuthenticator", + "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key.", + HttpContext); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/SetPassword.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/SetPassword.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/SetPassword.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/SetPassword.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,87 @@ +@page "/Account/Manage/SetPassword" + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager + +Set password + +

    Set your password

    + +

    + You do not have a local username/password for this site. Add a local + account so you can log in without an external login. +

    +
    +
    + + + +
    + + + +
    +
    + + + +
    + +
    +
    +
    + +@code { + private string? message; + private ApplicationUser user = default!; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + + var hasPassword = await UserManager.HasPasswordAsync(user); + if (hasPassword) + { + RedirectManager.RedirectTo("Account/Manage/ChangePassword"); + } + } + + private async Task OnValidSubmitAsync() + { + var addPasswordResult = await UserManager.AddPasswordAsync(user, Input.NewPassword!); + if (!addPasswordResult.Succeeded) + { + message = $"Error: {string.Join(",", addPasswordResult.Errors.Select(error => error.Description))}"; + return; + } + + await SignInManager.RefreshSignInAsync(user); + RedirectManager.RedirectToCurrentPageWithStatus("Your password has been set.", HttpContext); + } + + private sealed class InputModel + { + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string? NewPassword { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string? ConfirmPassword { get; set; } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/TwoFactorAuthentication.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/TwoFactorAuthentication.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/TwoFactorAuthentication.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/TwoFactorAuthentication.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,101 @@ +@page "/Account/Manage/TwoFactorAuthentication" + +@using Microsoft.AspNetCore.Http.Features +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager + +Two-factor authentication (2FA) + + +

    Two-factor authentication (2FA)

    +@if (canTrack) +{ + if (is2faEnabled) + { + if (recoveryCodesLeft == 0) + { +
    + You have no recovery codes left. +

    You must generate a new set of recovery codes before you can log in with a recovery code.

    +
    + } + else if (recoveryCodesLeft == 1) + { +
    + You have 1 recovery code left. +

    You can generate a new set of recovery codes.

    +
    + } + else if (recoveryCodesLeft <= 3) + { +
    + You have @recoveryCodesLeft recovery codes left. +

    You should generate a new set of recovery codes.

    +
    + } + + if (isMachineRemembered) + { +
    + + + + } + + Disable 2FA + Reset recovery codes + } + +

    Authenticator app

    + @if (!hasAuthenticator) + { + Add authenticator app + } + else + { + Set up authenticator app + Reset authenticator app + } +} +else +{ +
    + Privacy and cookie policy have not been accepted. +

    You must accept the policy before you can enable two factor authentication.

    +
    +} + +@code { + private bool canTrack; + private bool hasAuthenticator; + private int recoveryCodesLeft; + private bool is2faEnabled; + private bool isMachineRemembered; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + protected override async Task OnInitializedAsync() + { + var user = await UserAccessor.GetRequiredUserAsync(HttpContext); + canTrack = HttpContext.Features.Get()?.CanTrack ?? true; + hasAuthenticator = await UserManager.GetAuthenticatorKeyAsync(user) is not null; + is2faEnabled = await UserManager.GetTwoFactorEnabledAsync(user); + isMachineRemembered = await SignInManager.IsTwoFactorClientRememberedAsync(user); + recoveryCodesLeft = await UserManager.CountRecoveryCodesAsync(user); + } + + private async Task OnSubmitForgetBrowserAsync() + { + await SignInManager.ForgetTwoFactorClientAsync(); + + RedirectManager.RedirectToCurrentPageWithStatus( + "The current browser has been forgotten. When you login again from this browser you will be prompted for your 2fa code.", + HttpContext); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/RegisterConfirmation.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/RegisterConfirmation.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/RegisterConfirmation.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/RegisterConfirmation.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,68 @@ +@page "/Account/RegisterConfirmation" + +@using System.Text +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject IEmailSender EmailSender +@inject NavigationManager NavigationManager +@inject IdentityRedirectManager RedirectManager + +Register confirmation + +

    Register confirmation

    + + + +@if (emailConfirmationLink is not null) +{ +

    + This app does not currently have a real email sender registered, see these docs for how to configure a real email sender. + Normally this would be emailed: Click here to confirm your account +

    +} +else +{ +

    Please check your email to confirm your account.

    +} + +@code { + private string? emailConfirmationLink; + private string? statusMessage; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromQuery] + private string? Email { get; set; } + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + protected override async Task OnInitializedAsync() + { + if (Email is null) + { + RedirectManager.RedirectTo(""); + } + + var user = await UserManager.FindByEmailAsync(Email); + if (user is null) + { + HttpContext.Response.StatusCode = StatusCodes.Status404NotFound; + statusMessage = "Error finding user for unspecified email"; + } + else if (EmailSender is IdentityNoOpEmailSender) + { + // Once you add a real email sender, you should remove this code that lets you confirm the account + var userId = await UserManager.GetUserIdAsync(user); + var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + emailConfirmationLink = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, + new Dictionary { ["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl }); + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Register.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Register.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Register.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Register.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,145 @@ +@page "/Account/Register" + +@using System.ComponentModel.DataAnnotations +@using System.Text +@using System.Text.Encodings.Web +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject IUserStore UserStore +@inject SignInManager SignInManager +@inject IEmailSender EmailSender +@inject ILogger Logger +@inject NavigationManager NavigationManager +@inject IdentityRedirectManager RedirectManager + +Register + +

    Register

    + +
    +
    + + + +

    Create a new account.

    +
    + +
    + + + +
    +
    + + + +
    +
    + + + +
    + +
    +
    +
    +
    +

    Use another service to register.

    +
    + +
    +
    +
    + +@code { + private IEnumerable? identityErrors; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = new(); + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + private string? Message => identityErrors is null ? null : $"Error: {string.Join(", ", identityErrors.Select(error => error.Description))}"; + + public async Task RegisterUser(EditContext editContext) + { + var user = CreateUser(); + + await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); + var emailStore = GetEmailStore(); + await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); + var result = await UserManager.CreateAsync(user, Input.Password); + + if (!result.Succeeded) + { + identityErrors = result.Errors; + return; + } + + Logger.LogInformation("User created a new account with password."); + + var userId = await UserManager.GetUserIdAsync(user); + var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, + new Dictionary { ["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl }); + + await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl)); + + if (UserManager.Options.SignIn.RequireConfirmedAccount) + { + RedirectManager.RedirectTo( + "Account/RegisterConfirmation", + new() { ["email"] = Input.Email, ["returnUrl"] = ReturnUrl }); + } + + await SignInManager.SignInAsync(user, isPersistent: false); + RedirectManager.RedirectTo(ReturnUrl); + } + + private ApplicationUser CreateUser() + { + try + { + return Activator.CreateInstance(); + } + catch + { + throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " + + $"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor."); + } + } + + private IUserEmailStore GetEmailStore() + { + if (!UserManager.SupportsUserEmail) + { + throw new NotSupportedException("The default UI requires a user store with email support."); + } + return (IUserEmailStore)UserStore; + } + + private sealed class InputModel + { + [Required] + [EmailAddress] + [Display(Name = "Email")] + public string Email { get; set; } = ""; + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } = ""; + + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } = ""; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResendEmailConfirmation.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResendEmailConfirmation.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResendEmailConfirmation.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResendEmailConfirmation.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,68 @@ +@page "/Account/ResendEmailConfirmation" + +@using System.ComponentModel.DataAnnotations +@using System.Text +@using System.Text.Encodings.Web +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject IEmailSender EmailSender +@inject NavigationManager NavigationManager +@inject IdentityRedirectManager RedirectManager + +Resend email confirmation + +

    Resend email confirmation

    +

    Enter your email.

    +
    + +
    +
    + + + +
    + + + +
    + +
    +
    +
    + +@code { + private string? message; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = new(); + + private async Task OnValidSubmitAsync() + { + var user = await UserManager.FindByEmailAsync(Input.Email!); + if (user is null) + { + message = "Verification email sent. Please check your email."; + return; + } + + var userId = await UserManager.GetUserIdAsync(user); + var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, + new Dictionary { ["userId"] = userId, ["code"] = code }); + await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl)); + + message = "Verification email sent. Please check your email."; + } + + private sealed class InputModel + { + [Required] + [EmailAddress] + public string Email { get; set; } = ""; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResetPasswordConfirmation.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResetPasswordConfirmation.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResetPasswordConfirmation.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResetPasswordConfirmation.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,7 @@ +@page "/Account/ResetPasswordConfirmation" +Reset password confirmation + +

    Reset password confirmation

    +

    + Your password has been reset. Please click here to log in. +

    diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResetPassword.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResetPassword.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResetPassword.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResetPassword.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,103 @@ +@page "/Account/ResetPassword" + +@using System.ComponentModel.DataAnnotations +@using System.Text +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorWeb_CSharp.Data + +@inject IdentityRedirectManager RedirectManager +@inject UserManager UserManager + +Reset password + +

    Reset password

    +

    Reset your password.

    +
    +
    +
    + + + + + + +
    + + + +
    +
    + + + +
    +
    + + + +
    + +
    +
    +
    + +@code { + private IEnumerable? identityErrors; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = new(); + + [SupplyParameterFromQuery] + private string? Code { get; set; } + + private string? Message => identityErrors is null ? null : $"Error: {string.Join(", ", identityErrors.Select(error => error.Description))}"; + + protected override void OnInitialized() + { + if (Code is null) + { + RedirectManager.RedirectTo("Account/InvalidPasswordReset"); + } + + Input.Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code)); + } + + private async Task OnValidSubmitAsync() + { + var user = await UserManager.FindByEmailAsync(Input.Email); + if (user is null) + { + // Don't reveal that the user does not exist + RedirectManager.RedirectTo("Account/ResetPasswordConfirmation"); + } + + var result = await UserManager.ResetPasswordAsync(user, Input.Code, Input.Password); + if (result.Succeeded) + { + RedirectManager.RedirectTo("Account/ResetPasswordConfirmation"); + } + + identityErrors = result.Errors; + } + + private sealed class InputModel + { + [Required] + [EmailAddress] + public string Email { get; set; } = ""; + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + public string Password { get; set; } = ""; + + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } = ""; + + [Required] + public string Code { get; set; } = ""; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,109 @@ +using System.Diagnostics; +using System.Security.Claims; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.Server; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using BlazorWeb_CSharp.Client; +using BlazorWeb_CSharp.Data; + +namespace BlazorWeb_CSharp.Components.Account; + +// This is a server-side AuthenticationStateProvider that revalidates the security stamp for the connected user +// every 30 minutes an interactive circuit is connected. It also uses PersistentComponentState to flow the +// authentication state to the client which is then fixed for the lifetime of the WebAssembly application. +internal sealed class PersistingRevalidatingAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider +{ + private readonly IServiceScopeFactory scopeFactory; + private readonly PersistentComponentState state; + private readonly IdentityOptions options; + + private readonly PersistingComponentStateSubscription subscription; + + private Task? authenticationStateTask; + + public PersistingRevalidatingAuthenticationStateProvider( + ILoggerFactory loggerFactory, + IServiceScopeFactory serviceScopeFactory, + PersistentComponentState persistentComponentState, + IOptions optionsAccessor) + : base(loggerFactory) + { + scopeFactory = serviceScopeFactory; + state = persistentComponentState; + options = optionsAccessor.Value; + + AuthenticationStateChanged += OnAuthenticationStateChanged; + subscription = state.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly); + } + + protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30); + + protected override async Task ValidateAuthenticationStateAsync( + AuthenticationState authenticationState, CancellationToken cancellationToken) + { + // Get the user manager from a new scope to ensure it fetches fresh data + await using var scope = scopeFactory.CreateAsyncScope(); + var userManager = scope.ServiceProvider.GetRequiredService>(); + return await ValidateSecurityStampAsync(userManager, authenticationState.User); + } + + private async Task ValidateSecurityStampAsync(UserManager userManager, ClaimsPrincipal principal) + { + var user = await userManager.GetUserAsync(principal); + if (user is null) + { + return false; + } + else if (!userManager.SupportsUserSecurityStamp) + { + return true; + } + else + { + var principalStamp = principal.FindFirstValue(options.ClaimsIdentity.SecurityStampClaimType); + var userStamp = await userManager.GetSecurityStampAsync(user); + return principalStamp == userStamp; + } + } + + private void OnAuthenticationStateChanged(Task task) + { + authenticationStateTask = task; + } + + private async Task OnPersistingAsync() + { + if (authenticationStateTask is null) + { + throw new UnreachableException($"Authentication state not set in {nameof(OnPersistingAsync)}()."); + } + + var authenticationState = await authenticationStateTask; + var principal = authenticationState.User; + + if (principal.Identity?.IsAuthenticated == true) + { + var userId = principal.FindFirst(options.ClaimsIdentity.UserIdClaimType)?.Value; + var email = principal.FindFirst(options.ClaimsIdentity.EmailClaimType)?.Value; + + if (userId != null && email != null) + { + state.PersistAsJson(nameof(UserInfo), new UserInfo + { + UserId = userId, + Email = email, + }); + } + } + } + + protected override void Dispose(bool disposing) + { + subscription.Dispose(); + AuthenticationStateChanged -= OnAuthenticationStateChanged; + base.Dispose(disposing); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/PersistingServerAuthenticationStateProvider.cs dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/PersistingServerAuthenticationStateProvider.cs --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/PersistingServerAuthenticationStateProvider.cs 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/PersistingServerAuthenticationStateProvider.cs 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,70 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.Server; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using BlazorWeb_CSharp.Client; + +namespace BlazorWeb_CSharp.Components.Account; + +// This is a server-side AuthenticationStateProvider that uses PersistentComponentState to flow the +// authentication state to the client which is then fixed for the lifetime of the WebAssembly application. +internal sealed class PersistingServerAuthenticationStateProvider : ServerAuthenticationStateProvider, IDisposable +{ + private readonly PersistentComponentState state; + private readonly IdentityOptions options; + + private readonly PersistingComponentStateSubscription subscription; + + private Task? authenticationStateTask; + + public PersistingServerAuthenticationStateProvider( + PersistentComponentState persistentComponentState, + IOptions optionsAccessor) + { + state = persistentComponentState; + options = optionsAccessor.Value; + + AuthenticationStateChanged += OnAuthenticationStateChanged; + subscription = state.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly); + } + + private void OnAuthenticationStateChanged(Task task) + { + authenticationStateTask = task; + } + + private async Task OnPersistingAsync() + { + if (authenticationStateTask is null) + { + throw new UnreachableException($"Authentication state not set in {nameof(OnPersistingAsync)}()."); + } + + var authenticationState = await authenticationStateTask; + var principal = authenticationState.User; + + if (principal.Identity?.IsAuthenticated == true) + { + var userId = principal.FindFirst(options.ClaimsIdentity.UserIdClaimType)?.Value; + var email = principal.FindFirst(options.ClaimsIdentity.EmailClaimType)?.Value; + + if (userId != null && email != null) + { + state.PersistAsJson(nameof(UserInfo), new UserInfo + { + UserId = userId, + Email = email, + }); + } + } + } + + public void Dispose() + { + subscription.Dispose(); + AuthenticationStateChanged -= OnAuthenticationStateChanged; + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/AccountLayout.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/AccountLayout.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/AccountLayout.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/AccountLayout.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,32 @@ +@inherits LayoutComponentBase +@*#if (UseWebAssembly && InteractiveAtRoot) +@layout BlazorWeb_CSharp.Client.Layout.MainLayout +##else +@layout BlazorWeb_CSharp.Components.Layout.MainLayout +##endif*@ +@inject NavigationManager NavigationManager + +@if (HttpContext is null) +{ +

    Loading...

    +} +else +{ + @Body +} + +@code { + [CascadingParameter] + private HttpContext? HttpContext { get; set; } + + protected override void OnParametersSet() + { + if (HttpContext is null) + { + // If this code runs, we're currently rendering in interactive mode, so there is no HttpContext. + // The identity pages need to set cookies, so they require an HttpContext. To achieve this we + // must transition back from interactive mode to a server-rendered page. + NavigationManager.Refresh(forceReload: true); + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ExternalLoginPicker.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ExternalLoginPicker.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ExternalLoginPicker.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ExternalLoginPicker.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,43 @@ +@using Microsoft.AspNetCore.Authentication +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject SignInManager SignInManager +@inject IdentityRedirectManager RedirectManager + +@if (externalLogins.Length == 0) +{ +
    +

    + There are no external authentication services configured. See this article + about setting up this ASP.NET application to support logging in via external services. +

    +
    +} +else +{ +
    +
    + + +

    + @foreach (var provider in externalLogins) + { + + } +

    +
    +
    +} + +@code { + private AuthenticationScheme[] externalLogins = []; + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + protected override async Task OnInitializedAsync() + { + externalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToArray(); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ManageLayout.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ManageLayout.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ManageLayout.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ManageLayout.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,17 @@ +@inherits LayoutComponentBase +@layout AccountLayout + +

    Manage your account

    + +
    +

    Change your account settings

    +
    +
    +
    + +
    +
    + @Body +
    +
    +
    diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ManageNavMenu.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ManageNavMenu.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ManageNavMenu.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ManageNavMenu.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,37 @@ +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject SignInManager SignInManager + + + +@code { + private bool hasExternalLogins; + + protected override async Task OnInitializedAsync() + { + hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/RedirectToLogin.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/RedirectToLogin.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/RedirectToLogin.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/RedirectToLogin.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,8 @@ +@inject NavigationManager NavigationManager + +@code { + protected override void OnInitialized() + { + NavigationManager.NavigateTo($"Account/Login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}", forceLoad: true); + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ShowRecoveryCodes.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ShowRecoveryCodes.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ShowRecoveryCodes.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/ShowRecoveryCodes.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,28 @@ + +

    Recovery codes

    + +
    +
    + @foreach (var recoveryCode in RecoveryCodes) + { +
    + @recoveryCode +
    + } +
    +
    + +@code { + [Parameter] + public string[] RecoveryCodes { get; set; } = []; + + [Parameter] + public string? StatusMessage { get; set; } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/StatusMessage.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/StatusMessage.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/StatusMessage.razor 1970-01-01 00:00:00.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Shared/StatusMessage.razor 2023-11-13 13:20:34.000000000 +0000 @@ -0,0 +1,29 @@ +@if (!string.IsNullOrEmpty(DisplayMessage)) +{ + var statusMessageClass = DisplayMessage.StartsWith("Error") ? "danger" : "success"; + +} + +@code { + private string? messageFromCookie; + + [Parameter] + public string? Message { get; set; } + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + private string? DisplayMessage => Message ?? messageFromCookie; + + protected override void OnInitialized() + { + messageFromCookie = HttpContext.Request.Cookies[IdentityRedirectManager.StatusCookieName]; + + if (messageFromCookie is not null) + { + HttpContext.Response.Cookies.Delete(IdentityRedirectManager.StatusCookieName); + } + } +} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/App.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/App.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/App.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/App.razor 2023-11-13 13:20:34.000000000 +0000 @@ -15,26 +15,62 @@ ##endif*@ @*#if (!InteractiveAtRoot) + ##elseif (IndividualLocalAuth) + ##elseif (UseServer && UseWebAssembly) - + ##elseif (UseServer) - + ##else - + ##endif*@ @*#if (!InteractiveAtRoot) - ##elseif (UseServer && UseWebAssembly) --> - + ##elseif (IndividualLocalAuth) + + ##elseif (UseServer && UseWebAssembly) + ##elseif (UseServer) - + ##else - + ##endif*@ +@*#if (!InteractiveAtRoot || !IndividualLocalAuth) +#elseif (UseServer && UseWebAssembly) + +@code { + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account") + ? null + : InteractiveAuto; +} +#elseif (UseServer) + +@code { + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account") + ? null + : InteractiveServer; +} +#else + +@code { + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account") + ? null + : InteractiveWebAssembly; +} +#endif*@ diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/ExternalLoginPicker.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/ExternalLoginPicker.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/ExternalLoginPicker.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/ExternalLoginPicker.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,47 +0,0 @@ -@using Microsoft.AspNetCore.Authentication -@using Microsoft.AspNetCore.Identity -@using BlazorWeb_CSharp.Components.Pages.Account -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject SignInManager SignInManager -@inject IdentityRedirectManager RedirectManager - -@if ((_externalLogins?.Count ?? 0) == 0) -{ -
    -

    - There are no external authentication services configured. See this article - about setting up this ASP.NET application to support logging in via external services. -

    -
    -} -else -{ -
    -
    - - -

    - @foreach (var provider in _externalLogins!) - { - - } -

    -
    -
    -} - -@code { - private IList? _externalLogins; - - [SupplyParameterFromQuery] - private string ReturnUrl { get; set; } = default!; - - protected override async Task OnInitializedAsync() - { - ReturnUrl ??= "/"; - - _externalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList(); - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/LogoutForm.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/LogoutForm.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/LogoutForm.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/LogoutForm.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ -@using Microsoft.AspNetCore.Identity -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject SignInManager SignInManager -@inject NavigationManager NavigationManager -@inject IdentityRedirectManager RedirectManager - -
    - - - - -@code { - [Parameter(CaptureUnmatchedValues = true)] - public IDictionary? AdditionalAttributes { get; set; } - - [SupplyParameterFromForm] - private string? ReturnUrl { get; set; } - - [CascadingParameter] - private HttpContext HttpContext { get; set; } = default!; - - private async Task OnSubmitAsync() - { - var user = HttpContext.User; - - if (SignInManager.IsSignedIn(user)) - { - await SignInManager.SignOutAsync(); - RedirectManager.RedirectTo(ReturnUrl ?? "/"); - } - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/ShowRecoveryCodes.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/ShowRecoveryCodes.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/ShowRecoveryCodes.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/ShowRecoveryCodes.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ - -

    Recovery codes

    - -
    -
    - @for (var row = 0; row < RecoveryCodes.Length; row += 2) - { - @RecoveryCodes[row] - -   - - @RecoveryCodes[row + 1] - -
    - } -
    -
    - -@code { - [Parameter] - public string[] RecoveryCodes { get; set; } = default!; - - [Parameter] - public string StatusMessage { get; set; } = default!; -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/StatusMessage.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/StatusMessage.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/StatusMessage.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/StatusMessage.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ -@using BlazorWeb_CSharp.Identity - -@{ - var message = Message ?? MessageFromCookie; - - if (MessageFromCookie is not null) - { - HttpContext.Response.Cookies.Delete(IdentityRedirectManager.StatusCookieName); - } -} - -@if (!string.IsNullOrEmpty(message)) -{ - var statusMessageClass = message.StartsWith("Error") ? "danger" : "success"; - -} - -@code { - [Parameter] - public string? Message { get; set; } - - [CascadingParameter] - private HttpContext HttpContext { get; set; } = default!; - - private string? MessageFromCookie => HttpContext.Request.Cookies[IdentityRedirectManager.StatusCookieName]; -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/_Imports.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/_Imports.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/_Imports.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/_Imports.razor 2023-11-13 13:20:34.000000000 +0000 @@ -6,6 +6,7 @@ @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.JSInterop @using BlazorWeb_CSharp diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/ManageLayout.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/ManageLayout.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/ManageLayout.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/ManageLayout.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ -@inherits LayoutComponentBase -@layout MainLayout - -

    Manage your account

    - -
    -

    Change your account settings

    -
    -
    -
    - -
    -
    - @Body -
    -
    -
    diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/ManageNavMenu.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/ManageNavMenu.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/ManageNavMenu.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/ManageNavMenu.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -@using Microsoft.AspNetCore.Identity; -@using BlazorWeb_CSharp.Data; - -@inject SignInManager SignInManager; - - - -@code { - private bool _hasExternalLogins; - - protected override async Task OnInitializedAsync() - { - _hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor 2023-11-13 13:20:34.000000000 +0000 @@ -1,5 +1,7 @@ @*#if (IndividualLocalAuth) -@using BlazorWeb_CSharp.Components.Identity +@implements IDisposable + +@inject NavigationManager NavigationManager ##endif*@ +@*#if (IndividualLocalAuth) + +@code { + private string? currentUrl; + + protected override void OnInitialized() + { + currentUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); + NavigationManager.LocationChanged += OnLocationChanged; + } + + private void OnLocationChanged(object? sender, LocationChangedEventArgs e) + { + currentUrl = NavigationManager.ToBaseRelativePath(e.Location); + StateHasChanged(); + } + + public void Dispose() + { + NavigationManager.LocationChanged -= OnLocationChanged; + } +} +##endif*@ + diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor.css dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor.css --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor.css 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor.css 2023-11-13 13:20:34.000000000 +0000 @@ -34,36 +34,36 @@ background-size: cover; } -.bi-house-door-fill { +.bi-house-door-fill-nav-menu { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); } -.bi-plus-square-fill { +.bi-plus-square-fill-nav-menu { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); } -.bi-list-nested { +.bi-list-nested-nav-menu { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); } /*#if (IndividualLocalAuth)*/ -.bi-lock { +.bi-lock-nav-menu { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath d='M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2zM5 8h6a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z'/%3E%3C/svg%3E"); } -.bi-person { +.bi-person-nav-menu { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person' viewBox='0 0 16 16'%3E%3Cpath d='M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0Zm4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4Zm-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664h10Z'/%3E%3C/svg%3E"); } -.bi-person-badge { +.bi-person-badge-nav-menu { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person-badge' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-3zM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0z'/%3E%3Cpath d='M4.5 0A2.5 2.5 0 0 0 2 2.5V14a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2.5A2.5 2.5 0 0 0 11.5 0h-7zM3 2.5A1.5 1.5 0 0 1 4.5 1h7A1.5 1.5 0 0 1 13 2.5v10.795a4.2 4.2 0 0 0-.776-.492C11.392 12.387 10.063 12 8 12s-3.392.387-4.224.803a4.2 4.2 0 0 0-.776.492V2.5z'/%3E%3C/svg%3E"); } -.bi-person-fill { +.bi-person-fill-nav-menu { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person-fill' viewBox='0 0 16 16'%3E%3Cpath d='M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3Zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z'/%3E%3C/svg%3E"); } -.bi-arrow-bar-left { +.bi-arrow-bar-left-nav-menu { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-arrow-bar-left' viewBox='0 0 16 16'%3E%3Cpath d='M12.5 15a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5ZM10 8a.5.5 0 0 1-.5.5H3.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L3.707 7.5H9.5a.5.5 0 0 1 .5.5Z'/%3E%3C/svg%3E"); } @@ -81,13 +81,16 @@ padding-bottom: 1rem; } - .nav-item ::deep a { + .nav-item ::deep .nav-link { color: #d7d7d7; + background: none; + border: none; border-radius: 4px; height: 3rem; display: flex; align-items: center; line-height: 3rem; + width: 100%; } .nav-item ::deep a.active { @@ -95,7 +98,7 @@ color: white; } -.nav-item ::deep a:hover { +.nav-item ::deep .nav-link:hover { background-color: rgba(255,255,255,0.1); color: white; } diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ConfirmEmailChange.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ConfirmEmailChange.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ConfirmEmailChange.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ConfirmEmailChange.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,64 +0,0 @@ -@page "/Account/ConfirmEmailChange" - -@using System.Text -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject UserManager UserManager -@inject SignInManager SignInManager -@inject UserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager - -Confirm email change - -

    Confirm email change

    - - - -@code { - private string? _message; - private ApplicationUser _user = default!; - - [SupplyParameterFromQuery] - private string? UserId { get; set; } - - [SupplyParameterFromQuery] - private string? Email { get; set; } - - [SupplyParameterFromQuery] - private string? Code { get; set; } - - protected override async Task OnInitializedAsync() - { - if (UserId is null || Email is null || Code is null) - { - RedirectManager.RedirectToWithStatus( - "/Account/Login", "Error: Invalid email change confirmation link."); - return; - } - - _user = await UserAccessor.GetRequiredUserAsync(); - - var code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code)); - var result = await UserManager.ChangeEmailAsync(_user, Email, code); - if (!result.Succeeded) - { - _message = "Error changing email."; - return; - } - - // In our UI email and user name are one and the same, so when we update the email - // we need to update the user name. - var setUserNameResult = await UserManager.SetUserNameAsync(_user, Email); - if (!setUserNameResult.Succeeded) - { - _message = "Error changing user name."; - return; - } - - await SignInManager.RefreshSignInAsync(_user); - _message = "Thank you for confirming your email change."; - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ConfirmEmail.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ConfirmEmail.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ConfirmEmail.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ConfirmEmail.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,49 +0,0 @@ -@page "/Account/ConfirmEmail" - -@using System.Text -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject UserManager UserManager -@inject IdentityRedirectManager RedirectManager - -Confirm email - -

    Confirm email

    - - -@code { - string? statusMessage; - - [SupplyParameterFromQuery] - public string? UserId { get; set; } - - [SupplyParameterFromQuery] - public string? Code { get; set; } - - protected override async Task OnInitializedAsync() - { - if (UserId == null || Code == null) - { - RedirectManager.RedirectTo("/"); - } - else - { - var user = await UserManager.FindByIdAsync(UserId); - if (user == null) - { - // Need a way to trigger a 404 from Blazor: https://github.com/dotnet/aspnetcore/issues/45654 - statusMessage = $"Error loading user with ID {UserId}"; - } - else - { - - var code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code)); - var result = await UserManager.ConfirmEmailAsync(user, code); - statusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email."; - } - } - } -} \ No newline at end of file diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ExternalLogin.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ExternalLogin.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ExternalLogin.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ExternalLogin.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,213 +0,0 @@ -@page "/Account/ExternalLogin" - -@using System.ComponentModel.DataAnnotations -@using System.Security.Claims -@using System.Text -@using System.Text.Encodings.Web -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.Identity.UI.Services -@using Microsoft.AspNetCore.WebUtilities -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject SignInManager SignInManager -@inject UserManager UserManager -@inject IUserStore UserStore -@inject IEmailSender EmailSender -@inject NavigationManager NavigationManager -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -@{ - var providerDisplayName = _externalLoginInfo.ProviderDisplayName; -} - -Register - - -

    Register

    -

    Associate your @providerDisplayName account.

    -
    - -
    - You've successfully authenticated with @providerDisplayName. - Please enter an email address for this site below and click the Register button to finish - logging in. -
    - -
    -
    - - - -
    - - - -
    - -
    -
    -
    - -@code { - public const string LoginCallbackAction = "LoginCallback"; - - private string? _message; - private ExternalLoginInfo _externalLoginInfo = default!; - private IUserEmailStore _emailStore = default!; - - [SupplyParameterFromQuery] - private string? RemoteError { get; set; } - - [CascadingParameter] - public HttpContext HttpContext { get; set; } = default!; - - [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; - - [SupplyParameterFromQuery] - private string ReturnUrl { get; set; } = default!; - - [SupplyParameterFromQuery] - private string? Action { get; set; } = default!; - - protected override async Task OnInitializedAsync() - { - Input ??= new(); - ReturnUrl ??= "/"; - - if (RemoteError is not null) - { - RedirectManager.RedirectToWithStatus("/Account/Login", "Error from external provider: " + RemoteError); - return; - } - - var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync(); - if (externalLoginInfo is null) - { - RedirectManager.RedirectToWithStatus("/Account/Login", "Error loading external login information."); - return; - } - - _externalLoginInfo = externalLoginInfo; - _emailStore = GetEmailStore(); - - if (HttpMethods.IsGet(HttpContext.Request.Method)) - { - if (Action == LoginCallbackAction) - { - await OnLoginCallbackAsync(); - return; - } - - // We should only reach this page via the login callback, so redirect back to - // the login page if we get here some other way. - RedirectManager.RedirectTo("/Account/Login"); - return; - } - } - - private async Task OnLoginCallbackAsync() - { - // Sign in the user with this external login provider if the user already has a login. - var result = await SignInManager.ExternalLoginSignInAsync( - _externalLoginInfo.LoginProvider, - _externalLoginInfo.ProviderKey, - isPersistent: false, - bypassTwoFactor: true); - if (result.Succeeded) - { - Logger.LogInformation( - "{Name} logged in with {LoginProvider} provider.", - _externalLoginInfo.Principal.Identity?.Name, - _externalLoginInfo.LoginProvider); - RedirectManager.RedirectTo(ReturnUrl); - return; - } - - if (result.IsLockedOut) - { - RedirectManager.RedirectTo("/Account/Lockout"); - return; - } - - // If the user does not have an account, then ask the user to create an account. - if (_externalLoginInfo.Principal.HasClaim(c => c.Type == ClaimTypes.Email)) - { - Input.Email = _externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email); - } - } - - private async Task OnValidSubmitAsync() - { - var user = CreateUser(); - - await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); - await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); - - var result = await UserManager.CreateAsync(user); - if (result.Succeeded) - { - result = await UserManager.AddLoginAsync(user, _externalLoginInfo); - if (result.Succeeded) - { - Logger.LogInformation("User created an account using {Name} provider.", _externalLoginInfo.LoginProvider); - - var userId = await UserManager.GetUserIdAsync(user); - var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); - code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); - - var callbackUrl = NavigationManager.GetUriWithQueryParameters( - $"{NavigationManager.BaseUri}Account/ConfirmEmail", - new Dictionary { { "userId", userId }, { "code", code } }); - await EmailSender.SendEmailAsync(Input.Email!, "Confirm your email", - $"Please confirm your account by clicking here."); - - // If account confirmation is required, we need to show the link if we don't have a real email sender - if (UserManager.Options.SignIn.RequireConfirmedAccount) - { - RedirectManager.RedirectTo("/Account/RegisterConfirmation", new() { ["Email"] = Input.Email }); - return; - } - - await SignInManager.SignInAsync(user, isPersistent: false, _externalLoginInfo.LoginProvider); - RedirectManager.RedirectTo(ReturnUrl); - return; - } - } - else - { - _message = $"Error: {string.Join(",", result.Errors.Select(error => error.Description))}"; - } - } - - private ApplicationUser CreateUser() - { - try - { - return Activator.CreateInstance(); - } - catch - { - throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " + - $"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor"); - } - } - - private IUserEmailStore GetEmailStore() - { - if (!UserManager.SupportsUserEmail) - { - throw new NotSupportedException("The default UI requires a user store with email support."); - } - return (IUserEmailStore)UserStore; - } - - private sealed class InputModel - { - [Required] - [EmailAddress] - public string? Email { get; set; } - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ForgotPasswordConfirmation.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ForgotPasswordConfirmation.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ForgotPasswordConfirmation.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ForgotPasswordConfirmation.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -@page "/Account/ForgotPasswordConfirmation" - -Forgot password confirmation - -

    Forgot password confirmation

    -

    - Please check your email to reset your password. -

    diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ForgotPassword.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ForgotPassword.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ForgotPassword.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ForgotPassword.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,74 +0,0 @@ -@page "/Account/ForgotPassword" - -@using System.ComponentModel.DataAnnotations -@using System.Text -@using System.Text.Encodings.Web -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.Identity.UI.Services -@using Microsoft.AspNetCore.WebUtilities -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject NavigationManager NavigationManager -@inject IdentityRedirectManager RedirectManager -@inject UserManager UserManager -@inject IEmailSender EmailSender - -Forgot your password? - -

    Forgot your password?

    -

    Enter your email.

    -
    -
    -
    - - - - -
    - - - -
    - -
    -
    -
    - -@code { - [SupplyParameterFromForm] - private InputModel Input { get; set; } = new(); - - private async Task OnValidSubmitAsync() - { - var user = await UserManager.FindByEmailAsync(Input.Email); - if (user is null || !(await UserManager.IsEmailConfirmedAsync(user))) - { - // Don't reveal that the user does not exist or is not confirmed - RedirectManager.RedirectTo("/Account/ForgotPasswordConfirmation"); - return; - } - - // For more information on how to enable account confirmation and password reset please - // visit https://go.microsoft.com/fwlink/?LinkID=532713 - var code = await UserManager.GeneratePasswordResetTokenAsync(user); - code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); - var callbackUrl = NavigationManager.GetUriWithQueryParameters( - $"{NavigationManager.BaseUri}Account/ResetPassword", - new Dictionary { { "code", code } }); - - await EmailSender.SendEmailAsync( - Input.Email, - "Reset Password", - $"Please reset your password by clicking here."); - - RedirectManager.RedirectTo("/Account/ForgotPasswordConfirmation"); - } - - private sealed class InputModel - { - [Required] - [EmailAddress] - public string Email { get; set; } = default!; - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/_Imports.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/_Imports.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/_Imports.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/_Imports.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -@using BlazorWeb_CSharp.Components.Identity diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/InvalidPasswordReset.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/InvalidPasswordReset.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/InvalidPasswordReset.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/InvalidPasswordReset.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -@page "/Account/InvalidPasswordReset" - -Invalid password reset - -

    Invalid password reset

    -

    - The password reset link is invalid. -

    diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/InvalidUser.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/InvalidUser.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/InvalidUser.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/InvalidUser.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -@page "/Account/InvalidUser" - -Invalid user - -

    Invalid user

    - - diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Lockout.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Lockout.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Lockout.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Lockout.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -@page "/Account/Lockout" - -Locked out - -
    -

    Locked out

    -

    This account has been locked out, please try again later.

    -
    diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Login.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Login.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Login.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Login.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,133 +0,0 @@ -@page "/Account/Login" - -@using System.ComponentModel.DataAnnotations -@using System.Text -@using Microsoft.AspNetCore.Authentication -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.WebUtilities -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject SignInManager SignInManager -@inject ILogger Logger -@inject NavigationManager NavigationManager -@inject IdentityRedirectManager RedirectManager - -Log in - -

    Log in

    -
    -
    -
    - - -

    Use a local account to log in.

    -
    - -
    - - - -
    -
    - - - -
    -
    - -
    -
    - -
    - -
    -
    -
    -
    -
    -

    Use another service to log in.

    -
    - -
    -
    -
    - -@code { - string? errorMessage; - - [CascadingParameter] - public HttpContext HttpContext { get; set; } = default!; - - [SupplyParameterFromForm] - public InputModel Input { get; set; } = default!; - - [SupplyParameterFromQuery] - public string ReturnUrl { get; set; } = ""; - - public class InputModel - { - [Required] - [EmailAddress] - public string Email { get; set; } = null!; - - [Required] - [DataType(DataType.Password)] - public string Password { get; set; } = null!; - - [Display(Name = "Remember me?")] - public bool RememberMe { get; set; } = false; - } - - protected override async Task OnInitializedAsync() - { - Input ??= new(); - ReturnUrl ??= "/"; - - if (HttpMethods.IsGet(HttpContext.Request.Method)) - { - // Clear the existing external cookie to ensure a clean login process - await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); - } - } - - public async Task LoginUser() - { - // This doesn't count login failures towards account lockout - // To enable password failures to trigger account lockout, set lockoutOnFailure: true - var result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); - if (result.Succeeded) - { - Logger.LogInformation("User logged in."); - RedirectManager.RedirectTo(ReturnUrl); - } - if (result.RequiresTwoFactor) - { - RedirectManager.RedirectTo( - "/Account/LoginWith2fa", - new() { ["ReturnUrl"] = ReturnUrl, ["RememberMe"] = Input.RememberMe }); - } - if (result.IsLockedOut) - { - Logger.LogWarning("User account locked out."); - RedirectManager.RedirectTo("/Account/Lockout"); - } - else - { - errorMessage = "Error: Invalid login attempt."; - } - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/LoginWith2fa.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/LoginWith2fa.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/LoginWith2fa.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/LoginWith2fa.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,109 +0,0 @@ -@page "/Account/LoginWith2fa" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Identity -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject SignInManager SignInManager -@inject UserManager UserManager -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Two-factor authentication - -

    Two-factor authentication

    -
    - -

    Your login is protected with an authenticator app. Enter your authenticator code below.

    -
    -
    - - - - - -
    - - - -
    -
    - -
    -
    - -
    -
    -
    -
    -

    - Don't have access to your authenticator device? You can - log in with a recovery code. -

    - -@code { - private string? _message; - private ApplicationUser _user = default!; - - [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; - - [SupplyParameterFromQuery] - private string? ReturnUrl { get; set; } - - [SupplyParameterFromQuery] - private bool RememberMe { get; set; } - - protected override async Task OnInitializedAsync() - { - Input ??= new(); - ReturnUrl ??= "/"; - - var user = await SignInManager.GetTwoFactorAuthenticationUserAsync(); - if (user is null) - { - throw new InvalidOperationException($"Unable to load two-factor authentication user."); - } - - _user = user; - } - - private async Task OnValidSubmitAsync() - { - var authenticatorCode = Input.TwoFactorCode!.Replace(" ", string.Empty).Replace("-", string.Empty); - var result = await SignInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, RememberMe, Input.RememberMachine); - var userId = await UserManager.GetUserIdAsync(_user); - - if (result.Succeeded) - { - Logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", _user.Id); - RedirectManager.RedirectTo(ReturnUrl ?? "/"); - } - else if (result.IsLockedOut) - { - Logger.LogWarning("User with ID '{UserId}' account locked out.", _user.Id); - RedirectManager.RedirectTo("/Account/Lockout"); - } - else - { - Logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", _user.Id); - _message = "Error: Invalid authenticator code."; - } - } - - private sealed class InputModel - { - [Required] - [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] - [DataType(DataType.Text)] - [Display(Name = "Authenticator code")] - public string? TwoFactorCode { get; set; } - - [Display(Name = "Remember this machine")] - public bool RememberMachine { get; set; } - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/LoginWithRecoveryCode.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/LoginWithRecoveryCode.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/LoginWithRecoveryCode.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/LoginWithRecoveryCode.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,94 +0,0 @@ -@page "/Account/LoginWithRecoveryCode" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.Mvc -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject SignInManager SignInManager -@inject UserManager UserManager -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Recovery code verification - -

    Recovery code verification

    -
    - -

    - You have requested to log in with a recovery code. This login will not be remembered until you provide - an authenticator app code at log in or disable 2FA and log in again. -

    -
    -
    - - - -
    - - - -
    - -
    -
    -
    - -@code { - private string? _message; - private ApplicationUser _user = default!; - - [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; - - [SupplyParameterFromQuery] - private string? ReturnUrl { get; set; } - - protected override async Task OnInitializedAsync() - { - Input ??= new(); - - // Ensure the user has gone through the username & password screen first - var user = await SignInManager.GetTwoFactorAuthenticationUserAsync(); - if (user is null) - { - throw new InvalidOperationException($"Unable to load two-factor authentication user."); - } - - _user = user; - } - - private async Task OnValidSubmitAsync() - { - var recoveryCode = Input.RecoveryCode!.Replace(" ", string.Empty); - - var result = await SignInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode); - - var userId = await UserManager.GetUserIdAsync(_user); - - if (result.Succeeded) - { - Logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", _user.Id); - RedirectManager.RedirectTo(ReturnUrl ?? "/"); - } - if (result.IsLockedOut) - { - Logger.LogWarning("User account locked out."); - RedirectManager.RedirectTo("/Account/Lockout"); - } - else - { - Logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", _user.Id); - _message = "Error: Invalid recovery code entered."; - } - } - - private sealed class InputModel - { - [Required] - [DataType(DataType.Text)] - [Display(Name = "Recovery Code")] - public string? RecoveryCode { get; set; } - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ChangePassword.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ChangePassword.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ChangePassword.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ChangePassword.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,97 +0,0 @@ -@page "/Account/Manage/ChangePassword" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Identity -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject UserManager UserManager -@inject SignInManager SignInManager -@inject UserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Change password - -

    Change password

    - -
    -
    - - - -
    - - - -
    -
    - - - -
    -
    - - - -
    - -
    -
    -
    - -@code { - private string? _message; - private ApplicationUser _user = default!; - private bool _hasPassword; - - [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; - - protected override async Task OnInitializedAsync() - { - Input ??= new(); - - _user = await UserAccessor.GetRequiredUserAsync(); - _hasPassword = await UserManager.HasPasswordAsync(_user); - if (!_hasPassword) - { - RedirectManager.RedirectTo("/Account/Manage/SetPassword"); - return; - } - } - - private async Task OnValidSubmitAsync() - { - var changePasswordResult = await UserManager.ChangePasswordAsync(_user, Input.OldPassword!, Input.NewPassword!); - if (!changePasswordResult.Succeeded) - { - _message = $"Error: {string.Join(",", changePasswordResult.Errors.Select(error => error.Description))}"; - return; - } - - await SignInManager.RefreshSignInAsync(_user); - Logger.LogInformation("User changed their password successfully."); - - RedirectManager.RedirectToCurrentPageWithStatus("Your password has been changed"); - } - - private sealed class InputModel - { - [Required] - [DataType(DataType.Password)] - [Display(Name = "Current password")] - public string? OldPassword { get; set; } - - [Required] - [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] - [DataType(DataType.Password)] - [Display(Name = "New password")] - public string? NewPassword { get; set; } - - [DataType(DataType.Password)] - [Display(Name = "Confirm new password")] - [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] - public string? ConfirmPassword { get; set; } - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/DeletePersonalData.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/DeletePersonalData.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/DeletePersonalData.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/DeletePersonalData.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,85 +0,0 @@ -@page "/Account/Manage/DeletePersonalData" - -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Identity -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject UserManager UserManager -@inject SignInManager SignInManager -@inject UserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Delete Personal Data - - - -

    Delete Personal Data

    - - - -
    - - - - @if (_requirePassword) - { -
    - - - -
    - } - -
    -
    - -@code { - private string? _message; - private ApplicationUser _user = default!; - private bool _requirePassword; - - [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; - - protected override async Task OnInitializedAsync() - { - Input ??= new(); - - _user = await UserAccessor.GetRequiredUserAsync(); - _requirePassword = await UserManager.HasPasswordAsync(_user); - } - - private async Task OnValidSubmitAsync() - { - if (_requirePassword && !await UserManager.CheckPasswordAsync(_user, Input.Password!)) - { - _message = "Error: Incorrect password."; - return; - } - - var result = await UserManager.DeleteAsync(_user); - var userId = await UserManager.GetUserIdAsync(_user); - if (!result.Succeeded) - { - throw new InvalidOperationException($"Unexpected error occurred deleting user."); - } - - await SignInManager.SignOutAsync(); - - Logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId); - - RedirectManager.RedirectToCurrentPage(); - } - - private sealed class InputModel - { - [DataType(DataType.Password)] - public string? Password { get; set; } - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Disable2fa.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Disable2fa.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Disable2fa.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Disable2fa.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,68 +0,0 @@ -@page "/Account/Manage/Disable2fa" - -@using Microsoft.AspNetCore.Identity -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject UserManager UserManager -@inject UserAccessor UserAccessor -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Disable two-factor authentication (2FA) - - -

    Disable two-factor authentication (2FA)

    - - - -
    -
    - - - -
    - -@code { - private ApplicationUser _user = default!; - - [CascadingParameter] - private HttpContext HttpContext { get; set; } = default!; - - protected override async Task OnInitializedAsync() - { - _user = await UserAccessor.GetRequiredUserAsync(); - - if (HttpMethods.IsGet(HttpContext.Request.Method)) - { - if (!await UserManager.GetTwoFactorEnabledAsync(_user)) - { - throw new InvalidOperationException($"Cannot disable 2FA for user as it's not currently enabled."); - } - return; - } - } - - private async Task OnSubmitAsync() - { - var disable2faResult = await UserManager.SetTwoFactorEnabledAsync(_user, false); - if (!disable2faResult.Succeeded) - { - throw new InvalidOperationException($"Unexpected error occurred disabling 2FA."); - } - - var userId = await UserManager.GetUserIdAsync(_user); - Logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", userId); - RedirectManager.RedirectToWithStatus( - "/Account/Manage/TwoFactorAuthentication", - "2fa has been disabled. You can reenable 2fa when you setup an authenticator app"); - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Email.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Email.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Email.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Email.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,123 +0,0 @@ -@page "/Account/Manage/Email" - -@using System.ComponentModel.DataAnnotations -@using System.Text -@using System.Text.Encodings.Web -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.Identity.UI.Services -@using Microsoft.AspNetCore.WebUtilities -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject UserManager UserManager -@inject UserAccessor UserAccessor -@inject IEmailSender EmailSender -@inject NavigationManager NavigationManager -@inject IdentityRedirectManager RedirectManager - -Manage email - -

    Manage email

    - - -
    -
    -
    - - - - - - @if (_isEmailConfirmed) - { -
    - -
    - -
    - -
    - } - else - { -
    - - - -
    - } -
    - - - -
    - -
    -
    -
    - -@code { - private ApplicationUser _user = default!; - private string? _email; - private bool _isEmailConfirmed; - - [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; - - protected override async Task OnInitializedAsync() - { - Input ??= new(); - - _user = await UserAccessor.GetRequiredUserAsync(); - _email = await UserManager.GetEmailAsync(_user); - _isEmailConfirmed = await UserManager.IsEmailConfirmedAsync(_user); - - Input.NewEmail ??= _email; - } - - private async Task OnValidSubmitAsync() - { - if (Input.NewEmail != _email) - { - var userId = await UserManager.GetUserIdAsync(_user); - var code = await UserManager.GenerateChangeEmailTokenAsync(_user, Input.NewEmail!); - code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); - var callbackUrl = NavigationManager.GetUriWithQueryParameters( - $"{NavigationManager.BaseUri}Account/ConfirmEmailChange", - new Dictionary { { "userId", userId }, { "email", Input.NewEmail }, { "code", code } }); - await EmailSender.SendEmailAsync( - Input.NewEmail!, - "Confirm your email", - $"Please confirm your account by clicking here."); - - RedirectManager.RedirectToCurrentPageWithStatus("Confirmation link to change email sent. Please check your email."); - return; - } - - RedirectManager.RedirectToCurrentPageWithStatus("Your email is unchanged."); - } - - private async Task OnSendEmailVerificationAsync() - { - var userId = await UserManager.GetUserIdAsync(_user); - var code = await UserManager.GenerateEmailConfirmationTokenAsync(_user); - code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); - var callbackUrl = NavigationManager.GetUriWithQueryParameters( - $"{NavigationManager.BaseUri}Account/ConfirmEmail", - new Dictionary { { "userId", userId }, { "code", code } }); - await EmailSender.SendEmailAsync( - _email!, - "Confirm your email", - $"Please confirm your account by clicking here."); - - RedirectManager.RedirectToCurrentPageWithStatus("Verification email sent. Please check your email."); - } - - private sealed class InputModel - { - [Required] - [EmailAddress] - [Display(Name = "New email")] - public string? NewEmail { get; set; } - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/EnableAuthenticator.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/EnableAuthenticator.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/EnableAuthenticator.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/EnableAuthenticator.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,174 +0,0 @@ -@page "/Account/Manage/EnableAuthenticator" - -@using System.ComponentModel.DataAnnotations -@using System.Globalization -@using System.Text -@using System.Text.Encodings.Web -@using Microsoft.AspNetCore.Identity -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject UserManager UserManager -@inject UserAccessor UserAccessor -@inject UrlEncoder UrlEncoder -@inject IdentityRedirectManager RedirectManager -@inject ILogger Logger - -Configure authenticator app - -@if (_recoveryCodes is not null) -{ - -} -else -{ - -

    Configure authenticator app

    -
    -

    To use an authenticator app go through the following steps:

    -
      -
    1. -

      - Download a two-factor authenticator app like Microsoft Authenticator for - Android and - iOS or - Google Authenticator for - Android and - iOS. -

      -
    2. -
    3. -

      Scan the QR Code or enter this key @_sharedKey into your two factor authenticator app. Spaces and casing do not matter.

      - -
      -
      -
    4. -
    5. -

      - Once you have scanned the QR code or input the key above, your two factor authentication app will provide you - with a unique code. Enter the code in the confirmation box below. -

      -
      -
      - - -
      - - - -
      - - -
      -
      -
      -
    6. -
    -
    -} - -@code { - private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6"; - - private ApplicationUser _user = default!; - private string? _sharedKey; - private string? _authenticatorUri; - - private IEnumerable? _recoveryCodes; - private string? _message; - - [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; - - protected override async Task OnInitializedAsync() - { - Input ??= new(); - - _user = await UserAccessor.GetRequiredUserAsync(); - - await LoadSharedKeyAndQrCodeUriAsync(_user); - } - - private async Task OnValidSubmitAsync() - { - // Strip spaces and hyphens - var verificationCode = Input.Code!.Replace(" ", string.Empty).Replace("-", string.Empty); - - var is2faTokenValid = await UserManager.VerifyTwoFactorTokenAsync( - _user, UserManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); - - if (!is2faTokenValid) - { - await LoadSharedKeyAndQrCodeUriAsync(_user); - RedirectManager.RedirectToCurrentPageWithStatus("Error: Verification code is invalid."); - return; - } - - await UserManager.SetTwoFactorEnabledAsync(_user, true); - var userId = await UserManager.GetUserIdAsync(_user); - Logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId); - - _message = "Your authenticator app has been verified."; - - if (await UserManager.CountRecoveryCodesAsync(_user) == 0) - { - _recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(_user, 10); - } - else - { - RedirectManager.RedirectToWithStatus("/Account/Manage/TwoFactorAuthentication", _message); - } - } - - private async ValueTask LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user) - { - // Load the authenticator key & QR code URI to display on the form - var unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user); - if (string.IsNullOrEmpty(unformattedKey)) - { - await UserManager.ResetAuthenticatorKeyAsync(user); - unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user); - } - - _sharedKey = FormatKey(unformattedKey!); - - var email = await UserManager.GetEmailAsync(user); - _authenticatorUri = GenerateQrCodeUri(email!, unformattedKey!); - } - - private string FormatKey(string unformattedKey) - { - var result = new StringBuilder(); - int currentPosition = 0; - while (currentPosition + 4 < unformattedKey.Length) - { - result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' '); - currentPosition += 4; - } - if (currentPosition < unformattedKey.Length) - { - result.Append(unformattedKey.AsSpan(currentPosition)); - } - - return result.ToString().ToLowerInvariant(); - } - - private string GenerateQrCodeUri(string email, string unformattedKey) - { - return string.Format( - CultureInfo.InvariantCulture, - AuthenticatorUriFormat, - UrlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"), - UrlEncoder.Encode(email), - unformattedKey); - } - - private sealed class InputModel - { - [Required] - [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] - [DataType(DataType.Text)] - [Display(Name = "Verification Code")] - public string? Code { get; set; } - } -} diff -Nru dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ExternalLogins.razor dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ExternalLogins.razor --- dotnet8-8.0.100-8.0.0~rc2/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ExternalLogins.razor 2023-10-18 18:08:29.000000000 +0000 +++ dotnet8-8.0.100-8.0.0/src/aspnetcore/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ExternalLogins.razor 1970-01-01 00:00:00.000000000 +0000 @@ -1,152 +0,0 @@ -@page "/Account/Manage/ExternalLogins" - -@using Microsoft.AspNetCore.Authentication -@using Microsoft.AspNetCore.Identity -@using Microsoft.AspNetCore.Mvc.ViewFeatures -@using BlazorWeb_CSharp.Data -@using BlazorWeb_CSharp.Identity - -@inject UserManager UserManager -@inject SignInManager SignInManager -@inject UserAccessor UserAccessor -@inject IUserStore UserStore -@inject IdentityRedirectManager RedirectManager - -Manage your external logins - - -@if (_currentLogins?.Count > 0) -{ -

    Registered Logins

    - - - @foreach (var login in _currentLogins) - { - - - - - } - -
    @login.ProviderDisplayName - @if (_showRemoveButton) - { -
    - -
    - - - -
    - - } - else - { - @:   - } -
    -} -@if (_otherLogins?.Count > 0) -{ -

    Add another service to log in.

    -
    -